mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 18:48:08 +00:00 
			
		
		
		
	UI bug fix: Kubernetes Role filter replace with explicit input filter (#27178)
* initial changes for new component template only handle actions in parent * add changelog * fix current kubernetes test * component test * remove concurrency task * make div and not form due to testing things * address pr feedback * Update ui/tests/integration/components/filter-input-explicit-test.js Co-authored-by: Noelle Daley <noelledaley@users.noreply.github.com> * Update filter-input-explicit-test.js * fix tests * make it a form and fix test: --------- Co-authored-by: Noelle Daley <noelledaley@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										3
									
								
								changelog/27178.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/27178.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | ```release-note:change | ||||||
|  | ui/kubernetes: Update the roles filter-input to use explicit search. | ||||||
|  | ``` | ||||||
							
								
								
									
										19
									
								
								ui/lib/core/addon/components/filter-input-explicit.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								ui/lib/core/addon/components/filter-input-explicit.hbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | {{! | ||||||
|  |   Copyright (c) HashiCorp, Inc. | ||||||
|  |   SPDX-License-Identifier: BUSL-1.1 | ||||||
|  | ~}} | ||||||
|  |  | ||||||
|  | <form {{on "submit" @handleSearch}}> | ||||||
|  |   <Hds::SegmentedGroup as |S|> | ||||||
|  |     <S.TextInput | ||||||
|  |       @value={{@query}} | ||||||
|  |       placeholder={{@placeholder}} | ||||||
|  |       aria-label="Search by path" | ||||||
|  |       size="32" | ||||||
|  |       {{on "input" @handleInput}} | ||||||
|  |       {{on "keydown" @handleKeyDown}} | ||||||
|  |       data-test-filter-input-explicit | ||||||
|  |     /> | ||||||
|  |     <S.Button @color="secondary" @text="Search" @icon="search" type="submit" data-test-filter-input-explicit-search /> | ||||||
|  |   </Hds::SegmentedGroup> | ||||||
|  | </form> | ||||||
							
								
								
									
										6
									
								
								ui/lib/core/app/components/filter-input-explicit.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								ui/lib/core/app/components/filter-input-explicit.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | /** | ||||||
|  |  * Copyright (c) HashiCorp, Inc. | ||||||
|  |  * SPDX-License-Identifier: BUSL-1.1 | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | export { default } from 'core/components/filter-input-explicit'; | ||||||
| @@ -6,8 +6,11 @@ | |||||||
| <TabPageHeader | <TabPageHeader | ||||||
|   @model={{@backend}} |   @model={{@backend}} | ||||||
|   @filterRoles={{not @promptConfig}} |   @filterRoles={{not @promptConfig}} | ||||||
|   @rolesFilterValue={{@filterValue}} |   @query={{this.query}} | ||||||
|   @breadcrumbs={{@breadcrumbs}} |   @breadcrumbs={{@breadcrumbs}} | ||||||
|  |   @handleSearch={{this.handleSearch}} | ||||||
|  |   @handleInput={{this.handleInput}} | ||||||
|  |   @handleKeyDown={{this.handleKeyDown}} | ||||||
| > | > | ||||||
|   {{#unless @promptConfig}} |   {{#unless @promptConfig}} | ||||||
|     <ToolbarLink @route="roles.create" @type="add" data-test-toolbar-roles-action> |     <ToolbarLink @route="roles.create" @type="add" data-test-toolbar-roles-action> | ||||||
|   | |||||||
| @@ -9,24 +9,58 @@ import { action } from '@ember/object'; | |||||||
| import { getOwner } from '@ember/application'; | import { getOwner } from '@ember/application'; | ||||||
| import errorMessage from 'vault/utils/error-message'; | import errorMessage from 'vault/utils/error-message'; | ||||||
| import { tracked } from '@glimmer/tracking'; | import { tracked } from '@glimmer/tracking'; | ||||||
|  | import keys from 'core/utils/key-codes'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @module Roles |  * @module Roles | ||||||
|  * RolesPage component is a child component to show list of roles |  * RolesPage component is a child component to show list of roles. | ||||||
|  |  * It also handles the filtering actions of roles. | ||||||
|  * |  * | ||||||
|  * @param {array} roles - array of roles |  * @param {array} roles - array of roles | ||||||
|  * @param {boolean} promptConfig - whether or not to display config cta |  * @param {boolean} promptConfig - whether or not to display config cta | ||||||
|  * @param {array} pageFilter - array of filtered roles |  * @param {string} filterValue - value of queryParam pageFilter | ||||||
|  * @param {array} breadcrumbs - breadcrumbs as an array of objects that contain label and route |  * @param {array} breadcrumbs - breadcrumbs as an array of objects that contain label and route | ||||||
|  */ |  */ | ||||||
| export default class RolesPageComponent extends Component { | export default class RolesPageComponent extends Component { | ||||||
|   @service flashMessages; |   @service flashMessages; | ||||||
|  |   @service router; | ||||||
|  |   @tracked query; | ||||||
|   @tracked roleToDelete = null; |   @tracked roleToDelete = null; | ||||||
|  |  | ||||||
|  |   constructor() { | ||||||
|  |     super(...arguments); | ||||||
|  |     this.query = this.args.filterValue; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   get mountPoint() { |   get mountPoint() { | ||||||
|     return getOwner(this).mountPoint; |     return getOwner(this).mountPoint; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   navigate(pageFilter) { | ||||||
|  |     const route = `${this.mountPoint}.roles.index`; | ||||||
|  |     const args = [route, { queryParams: { pageFilter: pageFilter || null } }]; | ||||||
|  |     this.router.transitionTo(...args); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @action | ||||||
|  |   handleKeyDown(event) { | ||||||
|  |     if (event.keyCode === keys.ESC) { | ||||||
|  |       // On escape, transition to roles index route. | ||||||
|  |       this.navigate(); | ||||||
|  |     } | ||||||
|  |     // ignore all other key events | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @action handleInput(evt) { | ||||||
|  |     this.query = evt.target.value; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @action | ||||||
|  |   handleSearch(evt) { | ||||||
|  |     evt.preventDefault(); | ||||||
|  |     this.navigate(this.query); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @action |   @action | ||||||
|   async onDelete(model) { |   async onDelete(model) { | ||||||
|     try { |     try { | ||||||
|   | |||||||
| @@ -28,10 +28,12 @@ | |||||||
| <Toolbar aria-label="items for managing kubernetes items"> | <Toolbar aria-label="items for managing kubernetes items"> | ||||||
|   {{#if @filterRoles}} |   {{#if @filterRoles}} | ||||||
|     <ToolbarFilters> |     <ToolbarFilters> | ||||||
|       <NavigateInput |       <FilterInputExplicit | ||||||
|         @filter={{@rolesFilterValue}} |         @query={{@query}} | ||||||
|         @placeholder="Filter roles" |         @placeholder="Filter roles" | ||||||
|         @urls={{hash list="vault.cluster.secrets.backend.kubernetes.roles"}} |         @handleSearch={{@handleSearch}} | ||||||
|  |         @handleInput={{@handleInput}} | ||||||
|  |         @handleKeyDown={{@handleKeyDown}} | ||||||
|       /> |       /> | ||||||
|     </ToolbarFilters> |     </ToolbarFilters> | ||||||
|   {{/if}} |   {{/if}} | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import kubernetesScenario from 'vault/mirage/scenarios/kubernetes'; | |||||||
| import kubernetesHandlers from 'vault/mirage/handlers/kubernetes'; | import kubernetesHandlers from 'vault/mirage/handlers/kubernetes'; | ||||||
| import authPage from 'vault/tests/pages/auth'; | import authPage from 'vault/tests/pages/auth'; | ||||||
| import { fillIn, visit, currentURL, click, currentRouteName } from '@ember/test-helpers'; | import { fillIn, visit, currentURL, click, currentRouteName } from '@ember/test-helpers'; | ||||||
|  | import { GENERAL } from 'vault/tests/helpers/general-selectors'; | ||||||
|  |  | ||||||
| module('Acceptance | kubernetes | roles', function (hooks) { | module('Acceptance | kubernetes | roles', function (hooks) { | ||||||
|   setupApplicationTest(hooks); |   setupApplicationTest(hooks); | ||||||
| @@ -30,7 +31,8 @@ module('Acceptance | kubernetes | roles', function (hooks) { | |||||||
|   test('it should filter roles', async function (assert) { |   test('it should filter roles', async function (assert) { | ||||||
|     await this.visitRoles(); |     await this.visitRoles(); | ||||||
|     assert.dom('[data-test-list-item-link]').exists({ count: 3 }, 'Roles list renders'); |     assert.dom('[data-test-list-item-link]').exists({ count: 3 }, 'Roles list renders'); | ||||||
|     await fillIn('[data-test-component="navigate-input"]', '1'); |     await fillIn(GENERAL.filterInputExplicit, '1'); | ||||||
|  |     await click(GENERAL.filterInputExplicitSearch); | ||||||
|     assert.dom('[data-test-list-item-link]').exists({ count: 1 }, 'Filtered roles list renders'); |     assert.dom('[data-test-list-item-link]').exists({ count: 1 }, 'Filtered roles list renders'); | ||||||
|     assert.ok(currentURL().includes('pageFilter=1'), 'pageFilter query param value is set'); |     assert.ok(currentURL().includes('pageFilter=1'), 'pageFilter query param value is set'); | ||||||
|   }); |   }); | ||||||
|   | |||||||
| @@ -20,6 +20,8 @@ export const GENERAL = { | |||||||
|  |  | ||||||
|   filter: (name: string) => `[data-test-filter="${name}"]`, |   filter: (name: string) => `[data-test-filter="${name}"]`, | ||||||
|   filterInput: '[data-test-filter-input]', |   filterInput: '[data-test-filter-input]', | ||||||
|  |   filterInputExplicit: '[data-test-filter-input-explicit]', | ||||||
|  |   filterInputExplicitSearch: '[data-test-filter-input-explicit-search]', | ||||||
|   confirmModalInput: '[data-test-confirmation-modal-input]', |   confirmModalInput: '[data-test-confirmation-modal-input]', | ||||||
|   confirmButton: '[data-test-confirm-button]', |   confirmButton: '[data-test-confirm-button]', | ||||||
|   confirmTrigger: '[data-test-confirm-action-trigger]', |   confirmTrigger: '[data-test-confirm-action-trigger]', | ||||||
|   | |||||||
| @@ -0,0 +1,61 @@ | |||||||
|  | /** | ||||||
|  |  * Copyright (c) HashiCorp, Inc. | ||||||
|  |  * SPDX-License-Identifier: BUSL-1.1 | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | import { module, test } from 'qunit'; | ||||||
|  | import { setupRenderingTest } from 'ember-qunit'; | ||||||
|  | import { render, typeIn, click } from '@ember/test-helpers'; | ||||||
|  | import hbs from 'htmlbars-inline-precompile'; | ||||||
|  | import { GENERAL } from 'vault/tests/helpers/general-selectors'; | ||||||
|  | import sinon from 'sinon'; | ||||||
|  |  | ||||||
|  | const handler = (e) => { | ||||||
|  |   // required because filter-input-explicit passes handleSearch on form submit | ||||||
|  |   if (e && e.preventDefault) e.preventDefault(); | ||||||
|  |   return; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | module('Integration | Component | filter-input-explicit', function (hooks) { | ||||||
|  |   setupRenderingTest(hooks); | ||||||
|  |  | ||||||
|  |   hooks.beforeEach(function () { | ||||||
|  |     this.handleSearch = sinon.spy(handler); | ||||||
|  |     this.handleInput = sinon.spy(); | ||||||
|  |     this.handleKeyDown = sinon.spy(); | ||||||
|  |     this.query = ''; | ||||||
|  |     this.placeholder = 'Filter roles'; | ||||||
|  |  | ||||||
|  |     this.renderComponent = () => { | ||||||
|  |       return render( | ||||||
|  |         hbs`<FilterInputExplicit aria-label="test-component" @placeholder={{this.placeholder}} @query={{this.query}} @handleSearch={{this.handleSearch}} @handleInput={{this.handleInput}} @handleKeyDown={{this.handleKeyDown}} />` | ||||||
|  |       ); | ||||||
|  |     }; | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   test('it renders', async function (assert) { | ||||||
|  |     this.query = 'foo'; | ||||||
|  |     await this.renderComponent(); | ||||||
|  |  | ||||||
|  |     assert | ||||||
|  |       .dom(GENERAL.filterInputExplicit) | ||||||
|  |       .hasAttribute('placeholder', 'Filter roles', 'Placeholder passed to input element'); | ||||||
|  |     assert.dom(GENERAL.filterInputExplicit).hasValue('foo', 'Value passed to input element'); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   test('it should call handleSearch on submit', async function (assert) { | ||||||
|  |     await this.renderComponent(); | ||||||
|  |     await typeIn(GENERAL.filterInputExplicit, 'bar'); | ||||||
|  |     await click(GENERAL.filterInputExplicitSearch); | ||||||
|  |     assert.ok(this.handleSearch.calledOnce, 'handleSearch was called once'); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   test('it should send keydown event on keydown', async function (assert) { | ||||||
|  |     await this.renderComponent(); | ||||||
|  |     await typeIn(GENERAL.filterInputExplicit, 'a'); | ||||||
|  |     await typeIn(GENERAL.filterInputExplicit, 'b'); | ||||||
|  |  | ||||||
|  |     assert.ok(this.handleKeyDown.calledTwice, 'handle keydown was called twice'); | ||||||
|  |     assert.ok(this.handleSearch.notCalled, 'handleSearch was not called on a keydown event'); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -10,6 +10,7 @@ import { setupMirage } from 'ember-cli-mirage/test-support'; | |||||||
| import { render, click } from '@ember/test-helpers'; | import { render, click } from '@ember/test-helpers'; | ||||||
| import hbs from 'htmlbars-inline-precompile'; | import hbs from 'htmlbars-inline-precompile'; | ||||||
| import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs'; | import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs'; | ||||||
|  | import { GENERAL } from 'vault/tests/helpers/general-selectors'; | ||||||
|  |  | ||||||
| module('Integration | Component | kubernetes | Page::Roles', function (hooks) { | module('Integration | Component | kubernetes | Page::Roles', function (hooks) { | ||||||
|   setupRenderingTest(hooks); |   setupRenderingTest(hooks); | ||||||
| @@ -58,7 +59,7 @@ module('Integration | Component | kubernetes | Page::Roles', function (hooks) { | |||||||
|       .dom('[data-test-toolbar-roles-action]') |       .dom('[data-test-toolbar-roles-action]') | ||||||
|       .doesNotExist('Create role', 'Toolbar action does not render when not configured'); |       .doesNotExist('Create role', 'Toolbar action does not render when not configured'); | ||||||
|     assert |     assert | ||||||
|       .dom('[data-test-nav-input]') |       .dom(GENERAL.filterInputExplicit) | ||||||
|       .doesNotExist('Roles filter input does not render when not configured'); |       .doesNotExist('Roles filter input does not render when not configured'); | ||||||
|     assert.dom('[data-test-config-cta]').exists('Config cta renders'); |     assert.dom('[data-test-config-cta]').exists('Config cta renders'); | ||||||
|   }); |   }); | ||||||
| @@ -70,7 +71,7 @@ module('Integration | Component | kubernetes | Page::Roles', function (hooks) { | |||||||
|     assert |     assert | ||||||
|       .dom('[data-test-toolbar-roles-action] svg') |       .dom('[data-test-toolbar-roles-action] svg') | ||||||
|       .hasClass('flight-icon-plus', 'Toolbar action has correct icon'); |       .hasClass('flight-icon-plus', 'Toolbar action has correct icon'); | ||||||
|     assert.dom('[data-test-nav-input]').exists('Roles filter input renders'); |     assert.dom(GENERAL.filterInputExplicit).exists('Roles filter input renders'); | ||||||
|     assert.dom('[data-test-empty-state-title]').hasText('No roles yet', 'Title renders'); |     assert.dom('[data-test-empty-state-title]').hasText('No roles yet', 'Title renders'); | ||||||
|     assert |     assert | ||||||
|       .dom('[data-test-empty-state-message]') |       .dom('[data-test-empty-state-message]') | ||||||
|   | |||||||
| @@ -9,6 +9,8 @@ import { setupEngine } from 'ember-engines/test-support'; | |||||||
| import { setupMirage } from 'ember-cli-mirage/test-support'; | import { setupMirage } from 'ember-cli-mirage/test-support'; | ||||||
| import { render } from '@ember/test-helpers'; | import { render } from '@ember/test-helpers'; | ||||||
| import hbs from 'htmlbars-inline-precompile'; | import hbs from 'htmlbars-inline-precompile'; | ||||||
|  | import { GENERAL } from 'vault/tests/helpers/general-selectors'; | ||||||
|  | import sinon from 'sinon'; | ||||||
|  |  | ||||||
| module('Integration | Component | kubernetes | TabPageHeader', function (hooks) { | module('Integration | Component | kubernetes | TabPageHeader', function (hooks) { | ||||||
|   setupRenderingTest(hooks); |   setupRenderingTest(hooks); | ||||||
| @@ -28,12 +30,18 @@ module('Integration | Component | kubernetes | TabPageHeader', function (hooks) | |||||||
|     this.model = this.store.peekRecord('secret-engine', 'kubernetes-test'); |     this.model = this.store.peekRecord('secret-engine', 'kubernetes-test'); | ||||||
|     this.mount = this.model.path.slice(0, -1); |     this.mount = this.model.path.slice(0, -1); | ||||||
|     this.breadcrumbs = [{ label: 'Secrets', route: 'secrets', linkExternal: true }, { label: this.mount }]; |     this.breadcrumbs = [{ label: 'Secrets', route: 'secrets', linkExternal: true }, { label: this.mount }]; | ||||||
|  |     this.handleSearch = sinon.spy(); | ||||||
|  |     this.handleInput = sinon.spy(); | ||||||
|  |     this.handleKeyDown = sinon.spy(); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   test('it should render breadcrumbs', async function (assert) { |   test('it should render breadcrumbs', async function (assert) { | ||||||
|     await render(hbs`<TabPageHeader @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} />`, { |     await render( | ||||||
|  |       hbs`<TabPageHeader @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} @handleSearch={{this.handleSearch}} @handleInput={{this.handleInput}} @handleKeyDown={{this.handleKeyDown}} />`, | ||||||
|  |       { | ||||||
|         owner: this.engine, |         owner: this.engine, | ||||||
|     }); |       } | ||||||
|  |     ); | ||||||
|     assert.dom('[data-test-breadcrumbs] li:nth-child(1) a').hasText('Secrets', 'Secrets breadcrumb renders'); |     assert.dom('[data-test-breadcrumbs] li:nth-child(1) a').hasText('Secrets', 'Secrets breadcrumb renders'); | ||||||
|  |  | ||||||
|     assert |     assert | ||||||
| @@ -42,9 +50,12 @@ module('Integration | Component | kubernetes | TabPageHeader', function (hooks) | |||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   test('it should render title', async function (assert) { |   test('it should render title', async function (assert) { | ||||||
|     await render(hbs`<TabPageHeader @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} />`, { |     await render( | ||||||
|  |       hbs`<TabPageHeader @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} @handleSearch={{this.handleSearch}} @handleInput={{this.handleInput}} @handleKeyDown={{this.handleKeyDown}} />`, | ||||||
|  |       { | ||||||
|         owner: this.engine, |         owner: this.engine, | ||||||
|     }); |       } | ||||||
|  |     ); | ||||||
|     assert |     assert | ||||||
|       .dom('[data-test-header-title] svg') |       .dom('[data-test-header-title] svg') | ||||||
|       .hasClass('flight-icon-kubernetes-color', 'Correct icon renders in title'); |       .hasClass('flight-icon-kubernetes-color', 'Correct icon renders in title'); | ||||||
| @@ -52,9 +63,12 @@ module('Integration | Component | kubernetes | TabPageHeader', function (hooks) | |||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   test('it should render tabs', async function (assert) { |   test('it should render tabs', async function (assert) { | ||||||
|     await render(hbs`<TabPageHeader @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} />`, { |     await render( | ||||||
|  |       hbs`<TabPageHeader @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} @handleSearch={{this.handleSearch}} @handleInput={{this.handleInput}} @handleKeyDown={{this.handleKeyDown}}/>`, | ||||||
|  |       { | ||||||
|         owner: this.engine, |         owner: this.engine, | ||||||
|     }); |       } | ||||||
|  |     ); | ||||||
|     assert.dom('[data-test-tab="overview"]').hasText('Overview', 'Overview tab renders'); |     assert.dom('[data-test-tab="overview"]').hasText('Overview', 'Overview tab renders'); | ||||||
|     assert.dom('[data-test-tab="roles"]').hasText('Roles', 'Roles tab renders'); |     assert.dom('[data-test-tab="roles"]').hasText('Roles', 'Roles tab renders'); | ||||||
|     assert.dom('[data-test-tab="config"]').hasText('Configuration', 'Configuration tab renders'); |     assert.dom('[data-test-tab="config"]').hasText('Configuration', 'Configuration tab renders'); | ||||||
| @@ -62,16 +76,16 @@ module('Integration | Component | kubernetes | TabPageHeader', function (hooks) | |||||||
|  |  | ||||||
|   test('it should render filter for roles', async function (assert) { |   test('it should render filter for roles', async function (assert) { | ||||||
|     await render( |     await render( | ||||||
|       hbs`<TabPageHeader @model={{this.model}} @filterRoles={{true}} @rolesFilterValue="test" @breadcrumbs={{this.breadcrumbs}} />`, |       hbs`<TabPageHeader @model={{this.model}} @filterRoles={{true}} @query="test" @breadcrumbs={{this.breadcrumbs}} @handleSearch={{this.handleSearch}} @handleInput={{this.handleInput}} @handleKeyDown={{this.handleKeyDown}} />`, | ||||||
|       { owner: this.engine } |       { owner: this.engine } | ||||||
|     ); |     ); | ||||||
|     assert.dom('[data-test-nav-input] input').hasValue('test', 'Filter renders with provided value'); |     assert.dom(GENERAL.filterInputExplicit).hasValue('test', 'Filter renders with provided value'); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   test('it should yield block for toolbar actions', async function (assert) { |   test('it should yield block for toolbar actions', async function (assert) { | ||||||
|     await render( |     await render( | ||||||
|       hbs` |       hbs` | ||||||
|       <TabPageHeader @model={{this.model}} @breadcrumbs={{this.breadcrumbs}}> |       <TabPageHeader @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} @handleSearch={{this.handleSearch}} @handleInput={{this.handleInput}} @handleKeyDown={{this.handleKeyDown}}> | ||||||
|         <span data-test-yield>It yields!</span> |         <span data-test-yield>It yields!</span> | ||||||
|       </TabPageHeader> |       </TabPageHeader> | ||||||
|     `, |     `, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Angel Garbarino
					Angel Garbarino