diff --git a/addon/components/utils/account-banner.hbs b/addon/components/utils/account-banner.hbs index a16d497b..d3792283 100644 --- a/addon/components/utils/account-banner.hbs +++ b/addon/components/utils/account-banner.hbs @@ -21,44 +21,58 @@ {{/if}} {{#if (or @subtitle (has-block "custom-subtitle"))}} -
- {{#if @subtitle}} - - {{@subtitle}} - - {{else if (has-block "custom-subtitle")}} - {{yield to="custom-subtitle"}} - {{/if}} - - {{#if this.canSelectItem}} - + {{#if @subtitle}} +
- {{#each @selectableItems as |item|}} - {{#if (has-block "selectable-item")}} - {{yield item this.closeSelectionDropdown to="selectable-item"}} - {{/if}} - {{/each}} + {{#if @required}} + + {{@subtitle}} + + {{else}} + + {{@subtitle}} + + {{/if}} + + {{#if this.canSelectItem}} + +
+ {{#each @selectableItems as |item|}} + {{#if (has-block "selectable-item")}} + {{yield item this.closeSelectionDropdown to="selectable-item"}} + {{/if}} + {{/each}} +
+ {{/if}}
- {{/if}} -
+ {{#if this.feedbackMessage}} + + {{this.feedbackMessage.value}} + + {{/if}} +
+ {{else if (has-block "custom-subtitle")}} + {{yield to="custom-subtitle"}} + {{/if}} {{/if}} diff --git a/addon/components/utils/account-banner.ts b/addon/components/utils/account-banner.ts index ffc9caf7..4872b9d8 100644 --- a/addon/components/utils/account-banner.ts +++ b/addon/components/utils/account-banner.ts @@ -3,6 +3,8 @@ import { isBlank } from '@ember/utils'; import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; +import { type FeedbackMessage } from '@upfluence/oss-components/components/o-s-s/input-container'; + type SkinType = 'success' | 'error' | 'warning'; export type Alert = { @@ -27,11 +29,21 @@ interface UtilsAccountBannerArgs { canSelectItem?: boolean; selectableItems?: any[]; + feedbackMessage?: FeedbackMessage; + required?: boolean; } export default class extends Component { @tracked displaySelectableItems: boolean = false; + get feedbackMessage(): FeedbackMessage | undefined { + return this.args.feedbackMessage; + } + + get isErrored(): boolean { + return this.feedbackMessage?.type === 'error'; + } + get disabledClass(): string { return this.args.disabled ? 'account-banner--disabled' : ''; } @@ -41,6 +53,7 @@ export default class extends Component { } get borderColorClass(): string { + if (this.isErrored) return 'account-banner--error'; if (this.args.skin) return `account-banner--${this.args.skin}`; return ''; } diff --git a/tests/integration/components/utils/account-banner-test.ts b/tests/integration/components/utils/account-banner-test.ts index 4396846f..63ac852a 100644 --- a/tests/integration/components/utils/account-banner-test.ts +++ b/tests/integration/components/utils/account-banner-test.ts @@ -10,6 +10,20 @@ module('Integration | Component | utils/account-banner', function (hooks) { setupRenderingTest(hooks); setupIntl(hooks); + module('rendering', function () { + test('it renders', async function (assert) { + await render(hbs``); + + assert.dom('.account-banner').exists(); + }); + + test('it forwards HTML attributes', async function (assert) { + await render(hbs``); + + assert.dom('.account-banner[data-test="banner"]').exists(); + }); + }); + module('modifier classes', function () { test('it renders with no modifier classes by default', async function (assert) { await render(hbs``); @@ -57,6 +71,27 @@ module('Integration | Component | utils/account-banner', function (hooks) { }); }); + module('icon / image', function () { + test('it renders @icon', async function (assert) { + await render(hbs``); + + assert.dom('.upf-badge').exists(); + }); + + test('it renders @image as a badge', async function (assert) { + await render(hbs``); + + assert.dom('.icon-in-badge').exists(); + }); + + test('@icon takes precedence over @image', async function (assert) { + await render(hbs``); + + assert.dom('.upf-badge').exists(); + assert.dom('.icon-in-badge').doesNotExist(); + }); + }); + module('title', function () { test('it renders @title', async function (assert) { await render(hbs``); @@ -89,7 +124,7 @@ module('Integration | Component | utils/account-banner', function (hooks) { test('it renders @subtitle', async function (assert) { await render(hbs``); - assert.dom('[data-control-name="account-banner-selected-item-label"]').hasText('subtitle'); + assert.dom('[data-control-name="account-banner-selected-item-label"]').containsText('subtitle'); }); test('it renders custom-subtitle block', async function (assert) { @@ -113,30 +148,36 @@ module('Integration | Component | utils/account-banner', function (hooks) { assert.dom('.custom-sub').doesNotExist(); }); + test('it marks subtitle as required when @required is true', async function (assert) { + await render(hbs``); + + assert.dom('[data-control-name="account-banner-selected-item-label"]').hasText('subtitle *'); + }); + + test('it does not mark subtitle as required when @required is not set', async function (assert) { + await render(hbs``); + + assert.dom('[data-control-name="account-banner-selected-item-label"]').hasText('subtitle'); + }); + test('account-banner__selection is not rendered without subtitle or custom-subtitle', async function (assert) { await render(hbs``); assert.dom('.account-banner__selection').doesNotExist(); }); - }); - module('actions block', function () { - test('it renders the actions block', async function (assert) { - await render(hbs` - - <:actions>action - - `); + test('unique-account class is applied when canSelectItem is false', async function (assert) { + await render(hbs``); - assert.dom('.action').exists(); + assert.dom('.account-banner__selection--unique-account').exists(); }); }); - module('dropdown', function () { + module('selection dropdown', function () { test('chevron is not rendered without canSelectItem', async function (assert) { await render(hbs``); - assert.dom('.fa-chevron-down').doesNotExist(); + assert.dom('.fa-chevron-down, [data-icon="chevron-down"]').doesNotExist(); }); test('chevron is not rendered with only 1 item', async function (assert) { @@ -149,10 +190,12 @@ module('Integration | Component | utils/account-banner', function (hooks) { /> `); - assert.dom('[data-icon="chevron-down"]').doesNotExist(); + assert.dom('.fa-chevron-down, [data-icon="chevron-down"]').doesNotExist(); }); test('chevron is rendered with canSelectItem and multiple items', async function (assert) { + this.set('items', SELECTABLE_ITEMS); + await render(hbs` `); - this.set('items', SELECTABLE_ITEMS); assert.dom('.fa-chevron-down, [data-icon="chevron-down"]').exists(); }); @@ -286,11 +328,45 @@ module('Integration | Component | utils/account-banner', function (hooks) { assert.strictEqual(findAll('.item').length, SELECTABLE_ITEMS.length); }); + }); - test('unique-account class applied when canSelectItem is false', async function (assert) { + module('feedback message', function () { + test('it does not render any feedback message by default', async function (assert) { await render(hbs``); - assert.dom('.account-banner__selection--unique-account').exists(); + assert.dom('.account-banner__selection + span').doesNotExist(); + }); + + test('it renders an error feedback message', async function (assert) { + this.set('feedback', { type: 'error', value: 'Required field' }); + await render(hbs``); + + assert.dom('.account-banner__selection + span').exists(); + assert.dom('.account-banner__selection + span').hasClass('font-color-error-500'); + assert.dom('.account-banner__selection + span').hasText('Required field'); + }); + + test('it renders a warning feedback message', async function (assert) { + this.set('feedback', { type: 'warning', value: 'Be careful' }); + await render(hbs``); + + assert.dom('.account-banner__selection + span').exists(); + assert.dom('.account-banner__selection + span').hasClass('font-color-warning-500'); + assert.dom('.account-banner__selection + span').hasText('Be careful'); + }); + + test('it applies error border class when feedbackMessage type is error', async function (assert) { + this.set('feedback', { type: 'error', value: 'Required field' }); + await render(hbs``); + + assert.dom('.account-banner').hasClass('account-banner--error'); + }); + + test('it does not apply error border class when feedbackMessage type is warning', async function (assert) { + this.set('feedback', { type: 'warning', value: 'Be careful' }); + await render(hbs``); + + assert.dom('.account-banner').hasNoClass('account-banner--error'); }); }); @@ -328,17 +404,15 @@ module('Integration | Component | utils/account-banner', function (hooks) { }); }); - module('image / icon', function () { - test('it renders @image as a badge', async function (assert) { - await render(hbs``); - - assert.dom('.icon-in-badge').exists(); - }); - - test('it renders @icon', async function (assert) { - await render(hbs``); + module('actions block', function () { + test('it renders the actions block', async function (assert) { + await render(hbs` + + <:actions>action + + `); - assert.dom('.upf-badge').exists(); + assert.dom('.action').exists(); }); }); });