mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 18:48:08 +00:00 
			
		
		
		
	Ui/transform templates (#9981)
Add CRUD capabilities on transform templates. Disallow read or edit for built-ins.
This commit is contained in:
		| @@ -55,12 +55,11 @@ export default ApplicationAdapter.extend({ | ||||
|  | ||||
|   queryRecord(store, type, query) { | ||||
|     return this.ajax(this.url(query.backend, type.modelName, query.id), 'GET').then(result => { | ||||
|       // CBS TODO: Add name to response and unmap name <> id on models | ||||
|       return { | ||||
|         id: query.id, | ||||
|         ...result, | ||||
|       }; | ||||
|     }); | ||||
|   }, | ||||
|  | ||||
|   // buildUrl(modelName, id, snapshot, requestType, query, returns) {}, | ||||
| }); | ||||
|   | ||||
							
								
								
									
										32
									
								
								ui/app/components/transform-list-item.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								ui/app/components/transform-list-item.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| /** | ||||
|  * @module TransformListItem | ||||
|  * TransformListItem components are used for the list items for the Transform Secret Engines for all but Transformations. | ||||
|  * This component automatically handles read-only list items if capabilities are not granted or the item is internal only. | ||||
|  * | ||||
|  * @example | ||||
|  * ```js | ||||
|  * <TransformListItem @item={item} @itemPath="role/my-item" @itemType="role" /> | ||||
|  * ``` | ||||
|  * @param {object} item - item refers to the model item used on the list item partial | ||||
|  * @param {string} itemPath - usually the id of the item, but can be prefixed with the model type (see transform/role) | ||||
|  * @param {string} [itemType] - itemType is used to calculate whether an item is readable or | ||||
|  */ | ||||
|  | ||||
| import { computed } from '@ember/object'; | ||||
| import Component from '@ember/component'; | ||||
|  | ||||
| export default Component.extend({ | ||||
|   item: null, | ||||
|   itemPath: '', | ||||
|   itemType: '', | ||||
|  | ||||
|   itemViewable: computed('item', 'itemType', function() { | ||||
|     const item = this.get('item'); | ||||
|     if (this.itemType === 'alphabet' || this.itemType === 'template') { | ||||
|       return !item.get('id').startsWith('builtin/'); | ||||
|     } | ||||
|     return true; | ||||
|   }), | ||||
|  | ||||
|   backendType: 'transform', | ||||
| }); | ||||
							
								
								
									
										3
									
								
								ui/app/components/transform-template-edit.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								ui/app/components/transform-template-edit.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| import TransformBase from './transform-edit-base'; | ||||
|  | ||||
| export default TransformBase.extend({}); | ||||
| @@ -80,15 +80,14 @@ const SECRET_BACKENDS = { | ||||
|         editComponent: 'transform-role-edit', | ||||
|       }, | ||||
|       { | ||||
|         name: 'templates', | ||||
|         name: 'template', | ||||
|         modelPrefix: 'template/', | ||||
|         label: 'Templates', | ||||
|         searchPlaceholder: 'Filter templates', | ||||
|         item: 'templates', | ||||
|         item: 'template', | ||||
|         create: 'Create template', | ||||
|         tab: 'templates', | ||||
|         tab: 'template', | ||||
|         editComponent: 'transform-template-edit', | ||||
|         hideCreate: true, | ||||
|       }, | ||||
|       { | ||||
|         name: 'alphabets', | ||||
|   | ||||
| @@ -1,7 +1,52 @@ | ||||
| import { computed } from '@ember/object'; | ||||
| import DS from 'ember-data'; | ||||
| import { apiPath } from 'vault/macros/lazy-capabilities'; | ||||
| import attachCapabilities from 'vault/lib/attach-capabilities'; | ||||
| import { expandAttributeMeta } from 'vault/utils/field-to-attrs'; | ||||
|  | ||||
| export default DS.Model.extend({ | ||||
|   name: DS.attr('string'), | ||||
|   alphabet: DS.belongsTo('transform/alphabet'), | ||||
|   transformations: DS.hasMany('transformation'), | ||||
| const { attr } = DS; | ||||
|  | ||||
| const Model = DS.Model.extend({ | ||||
|   idPrefix: 'template/', | ||||
|   idForNav: computed('id', 'idPrefix', function() { | ||||
|     let modelId = this.id || ''; | ||||
|     return `${this.idPrefix}${modelId}`; | ||||
|   }), | ||||
|  | ||||
|   name: attr('string', { | ||||
|     label: 'Name', | ||||
|     fieldValue: 'id', | ||||
|     readOnly: true, | ||||
|     subText: | ||||
|       'Templates allow Vault to determine what and how to capture the value to be transformed. This cannot be edited later.', | ||||
|   }), | ||||
|   type: attr('string', { defaultValue: 'regex' }), | ||||
|   pattern: attr('string', { | ||||
|     subText: 'The template’s pattern defines the data format. Expressed in regex.', | ||||
|   }), | ||||
|   alphabet: attr('array', { | ||||
|     subText: | ||||
|       'Alphabet defines a set of characters (UTF-8) that is used for FPE to determine the validity of plaintext and ciphertext values. You can choose a built-in one, or create your own.', | ||||
|     editType: 'searchSelect', | ||||
|     fallbackComponent: 'string-list', | ||||
|     label: 'Alphabet', | ||||
|     models: ['transform/alphabet'], | ||||
|     selectLimit: 1, | ||||
|   }), | ||||
|  | ||||
|   attrs: computed('pattern', 'alphabet', function() { | ||||
|     let keys = ['name', 'pattern', 'alphabet']; | ||||
|     return expandAttributeMeta(this, keys); | ||||
|   }), | ||||
|  | ||||
|   editableAttrs: computed('pattern', 'alphabet', function() { | ||||
|     let keys = ['pattern', 'alphabet']; | ||||
|     return expandAttributeMeta(this, keys); | ||||
|   }), | ||||
|  | ||||
|   backend: attr('string', { readOnly: true }), | ||||
| }); | ||||
|  | ||||
| export default attachCapabilities(Model, { | ||||
|   updatePath: apiPath`${'backend'}/template/${'id'}`, | ||||
| }); | ||||
|   | ||||
| @@ -28,7 +28,7 @@ export default Route.extend({ | ||||
|       case 'role': | ||||
|         modelType = 'transform/role'; | ||||
|         break; | ||||
|       case 'templates': | ||||
|       case 'template': | ||||
|         modelType = 'transform/template'; | ||||
|         break; | ||||
|       case 'alphabets': | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import ApplicationSerializer from '../application'; | ||||
|  | ||||
| export default ApplicationSerializer.extend({ | ||||
|   extractLazyPaginatedData(payload) { | ||||
|     let ret; | ||||
|   | ||||
							
								
								
									
										34
									
								
								ui/app/serializers/transform/template.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								ui/app/serializers/transform/template.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| import ApplicationSerializer from '../application'; | ||||
|  | ||||
| export default ApplicationSerializer.extend({ | ||||
|   normalizeResponse(store, primaryModelClass, payload, id, requestType) { | ||||
|     payload.data.name = payload.id; | ||||
|     if (payload.data.alphabet) { | ||||
|       payload.data.alphabet = [payload.data.alphabet]; | ||||
|     } | ||||
|     return this._super(store, primaryModelClass, payload, id, requestType); | ||||
|   }, | ||||
|  | ||||
|   serialize() { | ||||
|     let json = this._super(...arguments); | ||||
|     if (json.alphabet && Array.isArray(json.alphabet)) { | ||||
|       // Templates should only ever have one alphabet | ||||
|       json.alphabet = json.alphabet[0]; | ||||
|     } | ||||
|     return json; | ||||
|   }, | ||||
|  | ||||
|   extractLazyPaginatedData(payload) { | ||||
|     let ret; | ||||
|     ret = payload.data.keys.map(key => { | ||||
|       let model = { | ||||
|         id: key, | ||||
|       }; | ||||
|       if (payload.backend) { | ||||
|         model.backend = payload.backend; | ||||
|       } | ||||
|       return model; | ||||
|     }); | ||||
|     return ret; | ||||
|   }, | ||||
| }); | ||||
							
								
								
									
										66
									
								
								ui/app/templates/components/transform-list-item.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								ui/app/templates/components/transform-list-item.hbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| {{#if (and itemViewable item.updatePath.canRead)}} | ||||
|   {{#linked-block | ||||
|     "vault.cluster.secrets.backend.show" | ||||
|     itemPath | ||||
|     class="list-item-row" | ||||
|     data-test-secret-link=itemPath | ||||
|     encode=true | ||||
|     queryParams=(secret-query-params backendType) | ||||
|   }} | ||||
|     <div class="columns is-mobile"> | ||||
|     <div class="column is-10"> | ||||
|       <SecretLink | ||||
|         @mode="show" | ||||
|         @secret={{item.id}} | ||||
|         @queryParams={{query-params type=modelType}} | ||||
|         @class="has-text-black has-text-weight-semibold"> | ||||
|         <Icon | ||||
|           @glyph='file-outline' | ||||
|           @class="has-text-grey-light"/> | ||||
|         {{if (eq item.id ' ') '(self)' (or item.keyWithoutParent item.id)}} | ||||
|       </SecretLink> | ||||
|     </div> | ||||
|     <div class="column has-text-right"> | ||||
|       {{#if (or item.updatePath.canRead item.updatePath.canUpdate)}} | ||||
|       <PopupMenu name="secret-menu"> | ||||
|         <nav class="menu"> | ||||
|           <ul class="menu-list"> | ||||
|             {{#if item.updatePath.canRead}} | ||||
|               <li class="action"> | ||||
|                 <SecretLink | ||||
|                   @mode="show" | ||||
|                   @secret={{itemPath}} | ||||
|                   @class="has-text-black has-text-weight-semibold"> | ||||
|                   Details | ||||
|                 </SecretLink> | ||||
|               </li> | ||||
|             {{/if}} | ||||
|             {{#if item.updatePath.canUpdate}} | ||||
|               <li class="action"> | ||||
|                 <SecretLink | ||||
|                   @mode="edit" | ||||
|                   @secret={{itemPath}} | ||||
|                   @class="has-text-black has-text-weight-semibold"> | ||||
|                   Edit | ||||
|                 </SecretLink> | ||||
|               </li> | ||||
|             {{/if}} | ||||
|           </ul> | ||||
|         </nav> | ||||
|       </PopupMenu> | ||||
|       {{/if}} | ||||
|     </div> | ||||
|   </div> | ||||
|   {{/linked-block}} | ||||
| {{else}} | ||||
|   <div class="list-item-row" data-test-view-only-list-item> | ||||
|     <div class="columns is-mobile"> | ||||
|       <div class="column is-12 has-text-grey has-text-weight-semibold"> | ||||
|         <Icon | ||||
|           @glyph='file-outline' | ||||
|           @class="has-text-grey-light"/> | ||||
|         {{if (eq item.id ' ') '(self)' (or item.keyWithoutParent item.id)}} | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| {{/if}} | ||||
							
								
								
									
										123
									
								
								ui/app/templates/components/transform-template-edit.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								ui/app/templates/components/transform-template-edit.hbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| <PageHeader as |p|> | ||||
|   <p.top> | ||||
|     {{key-value-header | ||||
|       baseKey=(hash display=model.id id=model.idForNav) | ||||
|       path="vault.cluster.secrets.backend.list" | ||||
|       mode=mode | ||||
|       root=root | ||||
|       showCurrent=true | ||||
|     }} | ||||
|   </p.top> | ||||
|   <p.levelLeft> | ||||
|     <h1 class="title is-3" data-test-secret-header="true"> | ||||
|       {{#if (eq mode "create") }} | ||||
|         Create Template | ||||
|       {{else if (eq mode "edit")}} | ||||
|         Edit Template | ||||
|       {{else}} | ||||
|         Template <code>{{model.id}}</code> | ||||
|       {{/if}} | ||||
|     </h1> | ||||
|   </p.levelLeft> | ||||
| </PageHeader> | ||||
|  | ||||
| {{#if (eq mode "show")}} | ||||
|   <Toolbar> | ||||
|     <ToolbarActions> | ||||
|       {{#if capabilities.canDelete}} | ||||
|         <button | ||||
|           class="toolbar-link" | ||||
|           onclick={{action "delete"}} | ||||
|           data-test-transformation-template-delete | ||||
|         > | ||||
|           Delete template | ||||
|         </button> | ||||
|       {{/if}} | ||||
|       {{#if capabilities.canUpdate }} | ||||
|         <ToolbarSecretLink | ||||
|           @secret={{concat model.idPrefix model.id}} | ||||
|           @mode="edit" | ||||
|           @data-test-edit-link=true | ||||
|           @replace=true | ||||
|         > | ||||
|           Edit template | ||||
|         </ToolbarSecretLink> | ||||
|       {{/if}} | ||||
|     </ToolbarActions> | ||||
|   </Toolbar> | ||||
| {{/if}} | ||||
|  | ||||
| {{#if (or (eq mode 'edit') (eq mode 'create'))}} | ||||
| <form onsubmit={{action "createOrUpdate" mode}}> | ||||
|   <div class="box is-sideless is-fullwidth is-marginless"> | ||||
|     {{message-error model=model}} | ||||
|     <NamespaceReminder @mode={{mode}} @noun="Transform role" /> | ||||
|     {{#each model.attrs as |attr|}} | ||||
|       {{#if (and (eq attr.name 'name') (eq mode 'edit')) }} | ||||
|         <label for="{{attr.name}}" class="is-label"> | ||||
|           {{attr.options.label}} | ||||
|         </label> | ||||
|         {{#if attr.options.subText}} | ||||
|           <p class="sub-text">{{attr.options.subText}}</p> | ||||
|         {{/if}} | ||||
|         <input | ||||
|           data-test-input={{attr.name}} | ||||
|           id={{attr.name}} | ||||
|           autocomplete="off" | ||||
|           spellcheck="false" | ||||
|           value={{or (get model attr.name) attr.options.defaultValue}} | ||||
|           readonly | ||||
|           class="field input is-readOnly" | ||||
|           type={{attr.type}} | ||||
|         /> | ||||
|       {{else}} | ||||
|         <FormField | ||||
|           data-test-field | ||||
|           @attr={{attr}} | ||||
|           @model={{model}} | ||||
|         /> | ||||
|       {{/if}} | ||||
|     {{/each}} | ||||
|   </div> | ||||
|   <div class="field is-grouped-split box is-fullwidth is-bottomless"> | ||||
|     <div class="control"> | ||||
|       <button | ||||
|         type="submit" | ||||
|         disabled={{buttonDisabled}} | ||||
|         class="button is-primary" | ||||
|         data-test-role-transform-create=true | ||||
|       > | ||||
|         {{#if (eq mode 'create')}} | ||||
|           Create role | ||||
|         {{else if (eq mode 'edit')}} | ||||
|           Save | ||||
|         {{/if}} | ||||
|       </button> | ||||
|       {{#secret-link | ||||
|         mode=(if (eq mode "create") "list" "show") | ||||
|         class="button" | ||||
|         secret=(concat model.idPrefix model.id) | ||||
|       }} | ||||
|         Cancel | ||||
|       {{/secret-link}} | ||||
|     </div> | ||||
|   </div> | ||||
| </form> | ||||
| {{else}} | ||||
| <div class="box is-fullwidth is-sideless is-paddingless is-marginless"> | ||||
|   {{#each model.attrs as |attr|}} | ||||
|     {{#if (eq attr.type "object")}} | ||||
|       {{info-table-row label=(capitalize (or attr.options.label (humanize (dasherize attr.name)))) value=(stringify (get model attr.name))}} | ||||
|     {{else if (eq attr.type "array")}} | ||||
|       {{info-table-row | ||||
|         label=(capitalize (or attr.options.label (humanize (dasherize attr.name)))) | ||||
|         value=(get model attr.name) | ||||
|         type=attr.type | ||||
|         viewAll=attr.name | ||||
|       }} | ||||
|     {{else}} | ||||
|       {{info-table-row label=(capitalize (or attr.options.label (humanize (dasherize attr.name)))) value=(get model attr.name)}} | ||||
|     {{/if}} | ||||
|   {{/each}} | ||||
| </div> | ||||
| {{/if}} | ||||
| @@ -1,77 +1,5 @@ | ||||
| {{!-- TODO do not let click if !canRead --}} | ||||
| {{#if (eq options.item "role")}} | ||||
|   {{#let (concat options.modelPrefix item.id) as |itemPath|}} | ||||
|     {{#linked-block | ||||
|       "vault.cluster.secrets.backend.show" | ||||
|       itemPath | ||||
|       class="list-item-row" | ||||
|       data-test-secret-link=itemPath | ||||
|       encode=true | ||||
|       queryParams=(secret-query-params backendModel.type) | ||||
|     }} | ||||
|       <div class="columns is-mobile"> | ||||
|       <div class="column is-10"> | ||||
|         <SecretLink | ||||
|           @mode="show" | ||||
|           @secret={{item.id}} | ||||
|           @queryParams={{if (eq backendModel.type "transform") (query-params tab="actions") ""}} | ||||
|           @class="has-text-black has-text-weight-semibold"> | ||||
|           <Icon | ||||
|             @glyph='file-outline' | ||||
|             @class="has-text-grey-light"/> | ||||
|           {{if (eq item.id ' ') '(self)' (or item.keyWithoutParent item.id)}} | ||||
|         </SecretLink> | ||||
|       </div> | ||||
|       <div class="column has-text-right"> | ||||
|         {{#if (or item.updatePath.canRead item.updatePath.canUpdate)}} | ||||
|         <PopupMenu name="secret-menu"> | ||||
|           <nav class="menu"> | ||||
|             <ul class="menu-list"> | ||||
|               {{#if (or item.versionPath.isLoading item.secretPath.isLoading)}} | ||||
|                 <li class="action"> | ||||
|                   <button disabled type="button" class="link button is-loading is-transparent"> | ||||
|                     loading | ||||
|                   </button> | ||||
|                 </li> | ||||
|               {{else}} | ||||
|                 {{#if item.updatePath.canRead}} | ||||
|                   <li class="action"> | ||||
|                     <SecretLink | ||||
|                       @mode="show" | ||||
|                       @secret={{itemPath}} | ||||
|                       @class="has-text-black has-text-weight-semibold"> | ||||
|                       Details | ||||
|                     </SecretLink> | ||||
|                   </li> | ||||
|                 {{/if}} | ||||
|                 {{#if item.updatePath.canUpdate}} | ||||
|                   <li class="action"> | ||||
|                     <SecretLink | ||||
|                       @mode="edit" | ||||
|                       @secret={{itemPath}} | ||||
|                       @class="has-text-black has-text-weight-semibold"> | ||||
|                       Edit | ||||
|                     </SecretLink> | ||||
|                   </li> | ||||
|                 {{/if}} | ||||
|               {{/if}} | ||||
|             </ul> | ||||
|           </nav> | ||||
|         </PopupMenu> | ||||
|         {{/if}} | ||||
|       </div> | ||||
|     </div> | ||||
|     {{/linked-block}} | ||||
|   {{/let}} | ||||
| {{else}} | ||||
|   <div class="list-item-row"> | ||||
|     <div class="columns is-mobile"> | ||||
|       <div class="column is-12 has-text-grey has-text-weight-semibold"> | ||||
|         <Icon | ||||
|           @glyph='file-outline' | ||||
|           @class="has-text-grey-light"/> | ||||
|         {{if (eq item.id ' ') '(self)' (or item.keyWithoutParent item.id)}} | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| {{/if}} | ||||
| <TransformListItem | ||||
|   @item={{item}} | ||||
|   @itemPath={{concat options.modelPrefix item.id}} | ||||
|   @itemType={{options.item}} | ||||
| /> | ||||
|   | ||||
							
								
								
									
										121
									
								
								ui/tests/integration/components/transform-list-item-test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								ui/tests/integration/components/transform-list-item-test.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | ||||
| import EmberObject from '@ember/object'; | ||||
| import { module, test } from 'qunit'; | ||||
| import { setupRenderingTest } from 'ember-qunit'; | ||||
| import { render, findAll, click } from '@ember/test-helpers'; | ||||
| import hbs from 'htmlbars-inline-precompile'; | ||||
|  | ||||
| module('Integration | Component | transform-list-item', function(hooks) { | ||||
|   setupRenderingTest(hooks); | ||||
|  | ||||
|   test('it renders un-clickable item if no read capability', async function(assert) { | ||||
|     let item = EmberObject.create({ | ||||
|       id: 'foo', | ||||
|       updatePath: { | ||||
|         canRead: false, | ||||
|         canDelete: true, | ||||
|         canUpdate: true, | ||||
|       }, | ||||
|     }); | ||||
|     this.set('itemPath', 'role/foo'); | ||||
|     this.set('itemType', 'role'); | ||||
|     this.set('item', item); | ||||
|     await render(hbs`<TransformListItem | ||||
|       @item={{item}} | ||||
|       @itemPath={{itemPath}} | ||||
|       @itemType={{itemType}} | ||||
|     />`); | ||||
|  | ||||
|     assert.dom('[data-test-view-only-list-item]').exists('shows view only list item'); | ||||
|     assert.dom('[data-test-view-only-list-item]').hasText(item.id, 'has correct label'); | ||||
|   }); | ||||
|  | ||||
|   test('it is clickable with details menu item if read capability', async function(assert) { | ||||
|     let item = EmberObject.create({ | ||||
|       id: 'foo', | ||||
|       updatePath: { | ||||
|         canRead: true, | ||||
|         canDelete: false, | ||||
|         canUpdate: false, | ||||
|       }, | ||||
|     }); | ||||
|     this.set('itemPath', 'template/foo'); | ||||
|     this.set('itemType', 'template'); | ||||
|     this.set('item', item); | ||||
|     await render(hbs`<TransformListItem | ||||
|       @item={{item}} | ||||
|       @itemPath={{itemPath}} | ||||
|       @itemType={{itemType}} | ||||
|     />`); | ||||
|  | ||||
|     assert.dom('[data-test-secret-link="template/foo"]').exists('shows clickable list item'); | ||||
|     await click('button.popup-menu-trigger'); | ||||
|     assert.equal(findAll('.popup-menu-content li').length, 1, 'has one option'); | ||||
|   }); | ||||
|  | ||||
|   test('it has details and edit menu item if read & edit capabilities', async function(assert) { | ||||
|     let item = EmberObject.create({ | ||||
|       id: 'foo', | ||||
|       updatePath: { | ||||
|         canRead: true, | ||||
|         canDelete: true, | ||||
|         canUpdate: true, | ||||
|       }, | ||||
|     }); | ||||
|     this.set('itemPath', 'alphabet/foo'); | ||||
|     this.set('itemType', 'alphabet'); | ||||
|     this.set('item', item); | ||||
|     await render(hbs`<TransformListItem | ||||
|       @item={{item}} | ||||
|       @itemPath={{itemPath}} | ||||
|       @itemType={{itemType}} | ||||
|     />`); | ||||
|  | ||||
|     assert.dom('[data-test-secret-link="alphabet/foo"]').exists('shows clickable list item'); | ||||
|     await click('button.popup-menu-trigger'); | ||||
|     assert.equal(findAll('.popup-menu-content li').length, 2, 'has both options'); | ||||
|   }); | ||||
|  | ||||
|   test('it is not clickable if built-in template with all capabilities', async function(assert) { | ||||
|     let item = EmberObject.create({ | ||||
|       id: 'builtin/foo', | ||||
|       updatePath: { | ||||
|         canRead: true, | ||||
|         canDelete: true, | ||||
|         canUpdate: true, | ||||
|       }, | ||||
|     }); | ||||
|     this.set('itemPath', 'template/builtin/foo'); | ||||
|     this.set('itemType', 'template'); | ||||
|     this.set('item', item); | ||||
|     await render(hbs`<TransformListItem | ||||
|       @item={{item}} | ||||
|       @itemPath={{itemPath}} | ||||
|       @itemType={{itemType}} | ||||
|     />`); | ||||
|  | ||||
|     assert.dom('[data-test-view-only-list-item]').exists('shows view only list item'); | ||||
|     assert.dom('[data-test-view-only-list-item]').hasText(item.id, 'has correct label'); | ||||
|   }); | ||||
|  | ||||
|   test('it is not clickable if built-in alphabet', async function(assert) { | ||||
|     let item = EmberObject.create({ | ||||
|       id: 'builtin/foo', | ||||
|       updatePath: { | ||||
|         canRead: true, | ||||
|         canDelete: true, | ||||
|         canUpdate: true, | ||||
|       }, | ||||
|     }); | ||||
|     this.set('itemPath', 'alphabet/builtin/foo'); | ||||
|     this.set('itemType', 'alphabet'); | ||||
|     this.set('item', item); | ||||
|     await render(hbs`<TransformListItem | ||||
|       @item={{item}} | ||||
|       @itemPath={{itemPath}} | ||||
|       @itemType={{itemType}} | ||||
|     />`); | ||||
|  | ||||
|     assert.dom('[data-test-view-only-list-item]').exists('shows view only list item'); | ||||
|     assert.dom('[data-test-view-only-list-item]').hasText(item.id, 'has correct label'); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,26 @@ | ||||
| import { module, skip } from 'qunit'; | ||||
| import { setupRenderingTest } from 'ember-qunit'; | ||||
| import { render } from '@ember/test-helpers'; | ||||
| import hbs from 'htmlbars-inline-precompile'; | ||||
|  | ||||
| module('Integration | Component | transform-template-edit', function(hooks) { | ||||
|   setupRenderingTest(hooks); | ||||
|  | ||||
|   skip('it renders', async function(assert) { | ||||
|     // Set any properties with this.set('myProperty', 'value'); | ||||
|     // Handle any actions with this.set('myAction', function(val) { ... }); | ||||
|  | ||||
|     await render(hbs`{{transform-template-edit}}`); | ||||
|  | ||||
|     assert.equal(this.element.textContent.trim(), ''); | ||||
|  | ||||
|     // Template block usage: | ||||
|     await render(hbs` | ||||
|       {{#transform-template-edit}} | ||||
|         template block text | ||||
|       {{/transform-template-edit}} | ||||
|     `); | ||||
|  | ||||
|     assert.equal(this.element.textContent.trim(), 'template block text'); | ||||
|   }); | ||||
| }); | ||||
		Reference in New Issue
	
	Block a user
	 Chelsea Shaw
					Chelsea Shaw