From 7f5d129e7aac7925179445fe81832b26502e23cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elodie=20Labb=C3=A9?= Date: Mon, 5 Jan 2026 17:48:41 +0100 Subject: [PATCH 01/13] Add alwaysExpanded arg to Layout::Sidebar, and an optional header named-block --- addon/components/o-s-s/layout/sidebar.hbs | 32 ++++++----- .../o-s-s/layout/sidebar.stories.js | 15 +++++- addon/components/o-s-s/layout/sidebar.ts | 9 +++- app/styles/organisms/sidebar.less | 7 +++ .../components/o-s-s/layout/sidebar-test.ts | 54 ++++++++++++++++++- 5 files changed, 99 insertions(+), 18 deletions(-) diff --git a/addon/components/o-s-s/layout/sidebar.hbs b/addon/components/o-s-s/layout/sidebar.hbs index 5962e7154..bf78c3375 100644 --- a/addon/components/o-s-s/layout/sidebar.hbs +++ b/addon/components/o-s-s/layout/sidebar.hbs @@ -1,17 +1,23 @@
- {{#if (or @homeParameters (and @logo @homeURL))}} -
- - <:icon> - brand - - + {{#if (has-block "header")}} +
+ {{yield (hash expanded=this.expanded) to="header"}}
+ {{else}} + {{#if (or @homeParameters (and @logo @homeURL))}} +
+ + <:icon> + brand + + +
+ {{/if}} {{/if}}
@@ -19,7 +25,7 @@ {{yield (hash expanded=this.expanded) to="content"}}
- {{#if @expandable}} + {{#if this.displayExpandedStateToggle}}
({ template: hbs`
- + <:content as |sidebar|> diff --git a/addon/components/o-s-s/layout/sidebar.ts b/addon/components/o-s-s/layout/sidebar.ts index eff2ec6c3..4806a2cd0 100644 --- a/addon/components/o-s-s/layout/sidebar.ts +++ b/addon/components/o-s-s/layout/sidebar.ts @@ -18,6 +18,7 @@ interface OSSLayoutSidebarArgs { logo?: string; homeURL?: string; expandable?: boolean; + alwaysExpanded?: boolean; } export default class OSSLayoutSidebar extends Component { @@ -41,6 +42,10 @@ export default class OSSLayoutSidebar extends Component { return classes.join(' '); } + get displayExpandedStateToggle(): boolean { + return Boolean(this.args.expandable) && !this.args.alwaysExpanded; + } + @action toggleExpandedState(): void { this.expanded = !this.expanded; @@ -52,7 +57,9 @@ export default class OSSLayoutSidebar extends Component { } private initializeSidebarState(): void { - this.expanded = Boolean(this.args.expandable) && this.upfLocalStorage.getItem(SIDEBAR_EXPANDED_STATE) !== 'false'; + this.expanded = + this.args.alwaysExpanded || + (Boolean(this.args.expandable) && this.upfLocalStorage.getItem(SIDEBAR_EXPANDED_STATE) !== 'false'); document.documentElement.style.setProperty( '--sidebar-width', 'var(--sidebar-' + (this.expanded ? 'expanded' : 'default') + '-width)' diff --git a/app/styles/organisms/sidebar.less b/app/styles/organisms/sidebar.less index 184e3d4c8..52bbbc80c 100644 --- a/app/styles/organisms/sidebar.less +++ b/app/styles/organisms/sidebar.less @@ -52,6 +52,13 @@ } } + &__header { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + } + &__content { padding: var(--spacing-px-12) var(--spacing-px-9); display: flex; diff --git a/tests/integration/components/o-s-s/layout/sidebar-test.ts b/tests/integration/components/o-s-s/layout/sidebar-test.ts index 21ae5abf1..e29b07f8e 100644 --- a/tests/integration/components/o-s-s/layout/sidebar-test.ts +++ b/tests/integration/components/o-s-s/layout/sidebar-test.ts @@ -1,7 +1,7 @@ import { hbs } from 'ember-cli-htmlbars'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; -import { click, render, settled } from '@ember/test-helpers'; +import { click, render } from '@ember/test-helpers'; import { setupIntl } from 'ember-intl/test-support'; import UPFLocalStorage from '@upfluence/oss-components/utils/upf-local-storage'; @@ -58,6 +58,18 @@ module('Integration | Component | o-s-s/layout/sidebar', function (hooks) { }); module('Named blocks', () => { + test('The header named-block is properly displayed', async function (assert) { + await render( + hbs` + + <:header> +

This is the header

+ +
` + ); + assert.dom('.oss-sidebar-container__header').hasText('This is the header'); + }); + test('The content named-block is properly displayed', async function (assert) { await render( hbs` @@ -133,7 +145,6 @@ module('Integration | Component | o-s-s/layout/sidebar', function (hooks) { assert.dom('.oss-sidebar-container').hasClass('oss-sidebar-container--expanded'); await click('[data-control-name="sidebar-expanded-state-toggle"]'); - await settled(); assert.dom('.oss-sidebar-container').doesNotHaveClass('oss-sidebar-container--expanded'); assert.equal(window.localStorage.getItem('_upf.oss-layout-sidebar-expanded'), 'false'); @@ -144,4 +155,43 @@ module('Integration | Component | o-s-s/layout/sidebar', function (hooks) { assert.equal(window.localStorage.getItem('_upf.oss-layout-sidebar-expanded'), 'true'); }); }); + + module('AlwaysExpanded behavior', (hooks) => { + hooks.beforeEach(function () { + this.expandable = true; + this.homeParameters = { + logo: '/assets/images/brand-icon.svg', + url: '/home' + }; + }); + + test('the toggle button is not displayed when alwaysExpanded is true', async function (assert) { + await render( + hbs`` + ); + assert.dom('.oss-sidebar-container__expand').doesNotExist(); + }); + + test('the toggle button is displayed when alwaysExpanded is false', async function (assert) { + await render( + hbs`` + ); + assert.dom('.oss-sidebar-container__expand').exists(); + assert.dom('.oss-sidebar-container__expand').hasText(this.intl.t('oss-components.sidebar.collapse')); + }); + + test('the sidebar is expanded when alwaysExpanded is true and expandable is true', async function (assert) { + await render( + hbs`` + ); + assert.dom('.oss-sidebar-container').hasClass('oss-sidebar-container--expanded'); + }); + + test('the sidebar is expanded when alwaysExpanded is true and expandable is false', async function (assert) { + await render( + hbs`` + ); + assert.dom('.oss-sidebar-container').hasClass('oss-sidebar-container--expanded'); + }); + }); }); From 9bcd5e05a424ae62fde8fa65ae8c5b8e8603f8df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elodie=20Labb=C3=A9?= Date: Tue, 6 Jan 2026 16:51:02 +0100 Subject: [PATCH 02/13] clean sidebar.hbs --- addon/components/o-s-s/layout/sidebar.hbs | 28 +++++++++++------------ 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/addon/components/o-s-s/layout/sidebar.hbs b/addon/components/o-s-s/layout/sidebar.hbs index bf78c3375..7606a1dbe 100644 --- a/addon/components/o-s-s/layout/sidebar.hbs +++ b/addon/components/o-s-s/layout/sidebar.hbs @@ -3,21 +3,19 @@
{{yield (hash expanded=this.expanded) to="header"}}
- {{else}} - {{#if (or @homeParameters (and @logo @homeURL))}} -
- - <:icon> - brand - - -
- {{/if}} + {{else if (or @homeParameters (and @logo @homeURL))}} +
+ + <:icon> + brand + + +
{{/if}}
From d00660cde9c74698c895a77cfa1bbfb18e32d455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elodie=20Labb=C3=A9?= Date: Thu, 8 Jan 2026 15:05:02 +0100 Subject: [PATCH 03/13] Do not display icon div when no icon is provided --- addon/components/o-s-s/layout/sidebar/item.hbs | 2 +- .../components/o-s-s/layout/sidebar/item-test.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/addon/components/o-s-s/layout/sidebar/item.hbs b/addon/components/o-s-s/layout/sidebar/item.hbs index 19a864cb3..c01e3e7df 100644 --- a/addon/components/o-s-s/layout/sidebar/item.hbs +++ b/addon/components/o-s-s/layout/sidebar/item.hbs @@ -16,7 +16,7 @@
{{yield (hash expanded=@expanded) to="icon"}}
- {{else}} + {{else if @icon}}
diff --git a/tests/integration/components/o-s-s/layout/sidebar/item-test.ts b/tests/integration/components/o-s-s/layout/sidebar/item-test.ts index ba42b9274..2daf2bc70 100644 --- a/tests/integration/components/o-s-s/layout/sidebar/item-test.ts +++ b/tests/integration/components/o-s-s/layout/sidebar/item-test.ts @@ -19,6 +19,12 @@ module('Integration | Component | oss/layout/sidebar/item', function (hooks) { assert.dom('.oss-sidebar-item .oss-sidebar-item__icon i').hasClass('fa-search'); }); + test('it does not render the icon div when no icon is provided', async function (assert) { + await render(hbs``); + + assert.dom('.oss-sidebar-item .oss-sidebar-item__icon').doesNotExist(); + }); + test('it renders the icon named block instead of the icon argument when present', async function (assert) { await render( hbs`<:icon>` From 086c80b1553bede19d407c11b07f03e1554f48d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elodie=20Labb=C3=A9?= Date: Thu, 8 Jan 2026 15:21:12 +0100 Subject: [PATCH 04/13] polish style --- addon/components/o-s-s/layout/sidebar/item.hbs | 2 +- app/styles/organisms/sidebar.less | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/addon/components/o-s-s/layout/sidebar/item.hbs b/addon/components/o-s-s/layout/sidebar/item.hbs index c01e3e7df..8f0d7e9a0 100644 --- a/addon/components/o-s-s/layout/sidebar/item.hbs +++ b/addon/components/o-s-s/layout/sidebar/item.hbs @@ -22,7 +22,7 @@
{{/if}} -
+
{{@label}}
diff --git a/app/styles/organisms/sidebar.less b/app/styles/organisms/sidebar.less index 52bbbc80c..83352f3aa 100644 --- a/app/styles/organisms/sidebar.less +++ b/app/styles/organisms/sidebar.less @@ -94,6 +94,8 @@ a.oss-sidebar-item { display: flex; justify-content: flex-start; align-items: center; + min-width: 28px; + min-height: 28px; color: var(--sidebar-font-color); font-size: var(--font-size-sm); @@ -115,6 +117,10 @@ a.oss-sidebar-item { overflow: hidden; white-space: nowrap; transition: width 300ms ease-in-out, opacity 300ms ease-in-out; + + &__no-icon { + padding-left: var(--spacing-px-9); + } } &__notification { From 196be49a5a02783f3732ebce371cc7fd12f98894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elodie=20Labb=C3=A9?= Date: Mon, 12 Jan 2026 11:27:48 +0100 Subject: [PATCH 05/13] add engine arg to anchor and item for transitions --- addon/components/o-s-s/anchor.ts | 8 +++++++- addon/components/o-s-s/layout/sidebar/item.hbs | 1 + addon/components/o-s-s/layout/sidebar/item.ts | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/addon/components/o-s-s/anchor.ts b/addon/components/o-s-s/anchor.ts index aaecda242..549285ff0 100644 --- a/addon/components/o-s-s/anchor.ts +++ b/addon/components/o-s-s/anchor.ts @@ -4,6 +4,7 @@ import RouterService from '@ember/routing/router-service'; interface OSSAnchorArgs { link: string; + engine?: string; noopener?: boolean; noreferrer?: boolean; } @@ -34,8 +35,13 @@ export default class OSSAnchor extends Component { } get isInternalRoute(): boolean { + console.log('here'); try { - return Boolean(this.router.urlFor(this.args.link)); + return Boolean( + this.args.engine + ? this.router.urlFor(this.args.engine + '.' + this.args.link) + : this.router.urlFor(this.args.link) + ); } catch (error) { return false; } diff --git a/addon/components/o-s-s/layout/sidebar/item.hbs b/addon/components/o-s-s/layout/sidebar/item.hbs index 8f0d7e9a0..dce06eb1c 100644 --- a/addon/components/o-s-s/layout/sidebar/item.hbs +++ b/addon/components/o-s-s/layout/sidebar/item.hbs @@ -1,5 +1,6 @@ Date: Tue, 13 Jan 2026 15:00:18 +0100 Subject: [PATCH 06/13] remove console log --- addon/components/o-s-s/anchor.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/addon/components/o-s-s/anchor.ts b/addon/components/o-s-s/anchor.ts index 549285ff0..fd6d003b3 100644 --- a/addon/components/o-s-s/anchor.ts +++ b/addon/components/o-s-s/anchor.ts @@ -35,7 +35,6 @@ export default class OSSAnchor extends Component { } get isInternalRoute(): boolean { - console.log('here'); try { return Boolean( this.args.engine From 44013a07d576c8e17e158ef4836b4897876b909f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elodie=20Labb=C3=A9?= Date: Mon, 19 Jan 2026 15:30:15 +0100 Subject: [PATCH 07/13] Cleanup anchor component, add doc and tests --- addon/components/o-s-s/anchor.stories.js | 10 ++++++++++ addon/components/o-s-s/anchor.ts | 7 ++----- .../integration/components/o-s-s/anchor-test.ts | 16 +++++++++++++++- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/addon/components/o-s-s/anchor.stories.js b/addon/components/o-s-s/anchor.stories.js index 5394b58e2..7ab91fc93 100644 --- a/addon/components/o-s-s/anchor.stories.js +++ b/addon/components/o-s-s/anchor.stories.js @@ -14,6 +14,16 @@ export default { }, control: { type: 'text' } }, + engine: { + description: 'Optional engine name to use for routing', + table: { + type: { + summary: 'string' + }, + defaultValue: { summary: '' } + }, + control: { type: 'text' } + }, noreferrer: { description: 'Enables the noreferrer rel attribute', table: { diff --git a/addon/components/o-s-s/anchor.ts b/addon/components/o-s-s/anchor.ts index fd6d003b3..2cb70b57f 100644 --- a/addon/components/o-s-s/anchor.ts +++ b/addon/components/o-s-s/anchor.ts @@ -35,12 +35,9 @@ export default class OSSAnchor extends Component { } get isInternalRoute(): boolean { + const route = this.args.engine ? this.args.engine + '.' + this.args.link : this.args.link; try { - return Boolean( - this.args.engine - ? this.router.urlFor(this.args.engine + '.' + this.args.link) - : this.router.urlFor(this.args.link) - ); + return Boolean(this.router.urlFor(route)); } catch (error) { return false; } diff --git a/tests/integration/components/o-s-s/anchor-test.ts b/tests/integration/components/o-s-s/anchor-test.ts index a07916f36..bb7f20f64 100644 --- a/tests/integration/components/o-s-s/anchor-test.ts +++ b/tests/integration/components/o-s-s/anchor-test.ts @@ -13,15 +13,29 @@ module('Integration | Component | o-s-s/anchor', function (hooks) { this.transitionToStub = sinon.stub(this.router, 'transitionTo'); }); - test('When link is registered in router it renders as a anchor element', async function (assert) { + test('When link is not registered in router it renders as a anchor element', async function (assert) { await render(hbs`test`); assert.dom('a').hasNoClass('ember-view'); + assert.dom('a').hasAttribute('href', 'http://www.google.fr'); + }); + + test('When engine is defined, it renders as a linkTo helper', async function (assert) { + const urlForStub = sinon.stub(this.router, 'urlFor').returns('/input'); + await render(hbs`test`); + + assert.ok(urlForStub.calledWithExactly('input.index')); + assert.dom('a').hasClass('ember-view'); + // '/' instead of '/input' because ember engines are doing the prefixing ('/input') internally + assert.dom('a').hasAttribute('href', '/'); }); test('When link is registered in router it renders as a linkTo helper', async function (assert) { + const urlForSpy = sinon.spy(this.router, 'urlFor'); await render(hbs`test`); + assert.ok(urlForSpy.calledWithExactly('index')); assert.dom('a').hasClass('ember-view'); + assert.dom('a').hasAttribute('href', '/'); }); }); From a652c7d3eaebb43968a314f99d5ba294be0284af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elodie=20Labb=C3=A9?= Date: Mon, 19 Jan 2026 15:30:36 +0100 Subject: [PATCH 08/13] add optional engine arg in GroupItem type --- addon/components/o-s-s/layout/sidebar/group.hbs | 1 + addon/components/o-s-s/layout/sidebar/group.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/addon/components/o-s-s/layout/sidebar/group.hbs b/addon/components/o-s-s/layout/sidebar/group.hbs index e38294490..299e53b3b 100644 --- a/addon/components/o-s-s/layout/sidebar/group.hbs +++ b/addon/components/o-s-s/layout/sidebar/group.hbs @@ -59,6 +59,7 @@ @locked={{item.locked}} @hasNotifications={{item.hasNotifications}} @link={{item.link}} + @engine={{item.engine}} @lockedAction={{item.lockedAction}} @action={{item.action}} data-control-name={{item.dataControlName}} diff --git a/addon/components/o-s-s/layout/sidebar/group.ts b/addon/components/o-s-s/layout/sidebar/group.ts index 1ffa5af48..2004f5b50 100644 --- a/addon/components/o-s-s/layout/sidebar/group.ts +++ b/addon/components/o-s-s/layout/sidebar/group.ts @@ -9,6 +9,7 @@ export type GroupItem = { label?: string; hasNotifications?: boolean; link: string; + engine?: string; active: boolean; dataControlName?: string; lockedAction?(): unknown; From e1fc39e600c0e813fdf9575ef8cac578628b77a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elodie=20Labb=C3=A9?= Date: Tue, 20 Jan 2026 11:35:42 +0100 Subject: [PATCH 09/13] rename engine to routePrefix, clean up css and tests --- addon/components/o-s-s/anchor.stories.js | 4 +-- addon/components/o-s-s/anchor.ts | 4 +-- .../components/o-s-s/layout/sidebar/group.hbs | 2 +- .../components/o-s-s/layout/sidebar/group.ts | 2 +- .../components/o-s-s/layout/sidebar/item.hbs | 4 +-- addon/components/o-s-s/layout/sidebar/item.ts | 2 +- app/styles/organisms/sidebar.less | 2 +- .../components/o-s-s/anchor-test.ts | 13 +++++----- .../o-s-s/layout/sidebar/item-test.ts | 25 ++++++++++--------- 9 files changed, 29 insertions(+), 29 deletions(-) diff --git a/addon/components/o-s-s/anchor.stories.js b/addon/components/o-s-s/anchor.stories.js index 7ab91fc93..9d801f0c9 100644 --- a/addon/components/o-s-s/anchor.stories.js +++ b/addon/components/o-s-s/anchor.stories.js @@ -14,8 +14,8 @@ export default { }, control: { type: 'text' } }, - engine: { - description: 'Optional engine name to use for routing', + routePrefix: { + description: 'Optional route prefix (engine) name to use for routing', table: { type: { summary: 'string' diff --git a/addon/components/o-s-s/anchor.ts b/addon/components/o-s-s/anchor.ts index 2cb70b57f..9e75f97ca 100644 --- a/addon/components/o-s-s/anchor.ts +++ b/addon/components/o-s-s/anchor.ts @@ -4,7 +4,7 @@ import RouterService from '@ember/routing/router-service'; interface OSSAnchorArgs { link: string; - engine?: string; + routePrefix?: string; noopener?: boolean; noreferrer?: boolean; } @@ -35,7 +35,7 @@ export default class OSSAnchor extends Component { } get isInternalRoute(): boolean { - const route = this.args.engine ? this.args.engine + '.' + this.args.link : this.args.link; + const route = this.args.routePrefix ? this.args.routePrefix + '.' + this.args.link : this.args.link; try { return Boolean(this.router.urlFor(route)); } catch (error) { diff --git a/addon/components/o-s-s/layout/sidebar/group.hbs b/addon/components/o-s-s/layout/sidebar/group.hbs index 299e53b3b..bd4c4538a 100644 --- a/addon/components/o-s-s/layout/sidebar/group.hbs +++ b/addon/components/o-s-s/layout/sidebar/group.hbs @@ -59,7 +59,7 @@ @locked={{item.locked}} @hasNotifications={{item.hasNotifications}} @link={{item.link}} - @engine={{item.engine}} + @routePrefix={{item.routePrefix}} @lockedAction={{item.lockedAction}} @action={{item.action}} data-control-name={{item.dataControlName}} diff --git a/addon/components/o-s-s/layout/sidebar/group.ts b/addon/components/o-s-s/layout/sidebar/group.ts index 2004f5b50..f63bddaa3 100644 --- a/addon/components/o-s-s/layout/sidebar/group.ts +++ b/addon/components/o-s-s/layout/sidebar/group.ts @@ -9,7 +9,7 @@ export type GroupItem = { label?: string; hasNotifications?: boolean; link: string; - engine?: string; + routePrefix?: string; active: boolean; dataControlName?: string; lockedAction?(): unknown; diff --git a/addon/components/o-s-s/layout/sidebar/item.hbs b/addon/components/o-s-s/layout/sidebar/item.hbs index dce06eb1c..8d9ba535e 100644 --- a/addon/components/o-s-s/layout/sidebar/item.hbs +++ b/addon/components/o-s-s/layout/sidebar/item.hbs @@ -1,6 +1,6 @@ {{/if}} -
+
{{@label}}
diff --git a/addon/components/o-s-s/layout/sidebar/item.ts b/addon/components/o-s-s/layout/sidebar/item.ts index 35595d5a8..f5b8ae6f8 100644 --- a/addon/components/o-s-s/layout/sidebar/item.ts +++ b/addon/components/o-s-s/layout/sidebar/item.ts @@ -5,7 +5,7 @@ import type { OSSTagArgs } from '@upfluence/oss-components/components/o-s-s/tag' interface OSSLayoutSidebarItemArgs { link: string; - engine?: string; + routePrefix?: string; icon?: string; locked?: boolean; hasNotifications?: boolean; diff --git a/app/styles/organisms/sidebar.less b/app/styles/organisms/sidebar.less index 83352f3aa..aa110a436 100644 --- a/app/styles/organisms/sidebar.less +++ b/app/styles/organisms/sidebar.less @@ -118,7 +118,7 @@ a.oss-sidebar-item { white-space: nowrap; transition: width 300ms ease-in-out, opacity 300ms ease-in-out; - &__no-icon { + &--no-icon { padding-left: var(--spacing-px-9); } } diff --git a/tests/integration/components/o-s-s/anchor-test.ts b/tests/integration/components/o-s-s/anchor-test.ts index bb7f20f64..037e0da4f 100644 --- a/tests/integration/components/o-s-s/anchor-test.ts +++ b/tests/integration/components/o-s-s/anchor-test.ts @@ -14,25 +14,24 @@ module('Integration | Component | o-s-s/anchor', function (hooks) { }); test('When link is not registered in router it renders as a anchor element', async function (assert) { - await render(hbs`test`); + await render(hbs`test`); assert.dom('a').hasNoClass('ember-view'); assert.dom('a').hasAttribute('href', 'http://www.google.fr'); }); - test('When engine is defined, it renders as a linkTo helper', async function (assert) { - const urlForStub = sinon.stub(this.router, 'urlFor').returns('/input'); - await render(hbs`test`); + test('When routePrefix is defined, it renders as a linkTo helper', async function (assert) { + const urlForStub = sinon.stub(this.router, 'urlFor').returns('/display'); + await render(hbs`test`); - assert.ok(urlForStub.calledWithExactly('input.index')); + assert.ok(urlForStub.calledWithExactly('display.index')); assert.dom('a').hasClass('ember-view'); - // '/' instead of '/input' because ember engines are doing the prefixing ('/input') internally assert.dom('a').hasAttribute('href', '/'); }); test('When link is registered in router it renders as a linkTo helper', async function (assert) { const urlForSpy = sinon.spy(this.router, 'urlFor'); - await render(hbs`test`); + await render(hbs`test`); assert.ok(urlForSpy.calledWithExactly('index')); assert.dom('a').hasClass('ember-view'); diff --git a/tests/integration/components/o-s-s/layout/sidebar/item-test.ts b/tests/integration/components/o-s-s/layout/sidebar/item-test.ts index 2daf2bc70..0343470c2 100644 --- a/tests/integration/components/o-s-s/layout/sidebar/item-test.ts +++ b/tests/integration/components/o-s-s/layout/sidebar/item-test.ts @@ -8,13 +8,13 @@ module('Integration | Component | oss/layout/sidebar/item', function (hooks) { setupRenderingTest(hooks); test('it renders', async function (assert) { - await render(hbs``); + await render(hbs``); assert.dom('.oss-sidebar-item').exists(); }); test('it renders the icon when present', async function (assert) { - await render(hbs``); + await render(hbs``); assert.dom('.oss-sidebar-item .oss-sidebar-item__icon i').hasClass('fa-search'); }); @@ -23,6 +23,7 @@ module('Integration | Component | oss/layout/sidebar/item', function (hooks) { await render(hbs``); assert.dom('.oss-sidebar-item .oss-sidebar-item__icon').doesNotExist(); + assert.dom('.oss-sidebar-item__label--no-icon').exists(); }); test('it renders the icon named block instead of the icon argument when present', async function (assert) { @@ -35,17 +36,17 @@ module('Integration | Component | oss/layout/sidebar/item', function (hooks) { module('Arguments', () => { test('Default value for locked is false', async function (assert) { - await render(hbs``); + await render(hbs``); assert.dom('.oss-sidebar-item__lock').doesNotExist(); }); test('When locked is true', async function (assert) { - await render(hbs``); + await render(hbs``); assert.dom('.oss-sidebar-item__lock').exists(); }); test('Default value for hasNotification is false', async function (assert) { - await render(hbs``); + await render(hbs``); assert.dom('.oss-sidebar-item__notification').doesNotExist(); }); @@ -56,12 +57,12 @@ module('Integration | Component | oss/layout/sidebar/item', function (hooks) { module('Expanded state', () => { test('the wrapper container is applied', async function (assert) { - await render(hbs``); + await render(hbs``); assert.dom('.oss-sidebar-item').hasClass('oss-sidebar-item--expanded'); }); test('the label is displayed', async function (assert) { - await render(hbs``); + await render(hbs``); assert.dom('.oss-sidebar-item__label').exists(); assert.dom('.oss-sidebar-item__label').hasText('Label'); }); @@ -78,7 +79,7 @@ module('Integration | Component | oss/layout/sidebar/item', function (hooks) { test('on click, it redirect to the @link attribute', async function (assert) { const router = this.owner.lookup('service:router'); await render( - hbs`` + hbs`` ); assert.equal(router.currentRouteName, null); @@ -88,7 +89,7 @@ module('Integration | Component | oss/layout/sidebar/item', function (hooks) { test('When locked is true lockedAction is triggered', async function (assert) { await render( - hbs`` + hbs`` ); await click('.oss-sidebar-item'); @@ -98,7 +99,7 @@ module('Integration | Component | oss/layout/sidebar/item', function (hooks) { test('on click, when item is not locked, the action is called ', async function (assert) { await render( - hbs`` + hbs`` ); await click('.oss-sidebar-item'); @@ -109,12 +110,12 @@ module('Integration | Component | oss/layout/sidebar/item', function (hooks) { module('Extra attributes', () => { test('passing an extra class is applied to the component', async function (assert) { - await render(hbs``); + await render(hbs``); assert.dom('.my-extra-class').exists(); }); test('passing data-control-name works', async function (assert) { - await render(hbs``); + await render(hbs``); let inputWrapper: Element | null = find('.oss-sidebar-item'); assert.equal(inputWrapper?.getAttribute('data-control-name'), 'layout-sidebar'); }); From c0657dad4ef1bea18b1b1fdb491b35dc7ef5cc46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elodie=20Labb=C3=A9?= Date: Fri, 23 Jan 2026 14:59:51 +0100 Subject: [PATCH 10/13] add disableAutoActive option to OSS::Anchor --- addon/components/o-s-s/anchor.hbs | 2 +- addon/components/o-s-s/anchor.stories.js | 11 +++++ addon/components/o-s-s/anchor.ts | 9 ++++ .../components/o-s-s/layout/sidebar/group.hbs | 1 + .../components/o-s-s/layout/sidebar/group.ts | 1 + .../components/o-s-s/layout/sidebar/item.hbs | 1 + .../o-s-s/layout/sidebar/item.stories.js | 10 ++++ addon/components/o-s-s/layout/sidebar/item.ts | 1 + .../components/o-s-s/anchor-test.ts | 48 ++++++++++++------- 9 files changed, 67 insertions(+), 17 deletions(-) diff --git a/addon/components/o-s-s/anchor.hbs b/addon/components/o-s-s/anchor.hbs index 9872feed6..4bdb4f7e9 100644 --- a/addon/components/o-s-s/anchor.hbs +++ b/addon/components/o-s-s/anchor.hbs @@ -1,5 +1,5 @@ {{#if this.isInternalRoute}} - {{yield}} + {{yield}} {{else}} {{yield}} {{/if}} \ No newline at end of file diff --git a/addon/components/o-s-s/anchor.stories.js b/addon/components/o-s-s/anchor.stories.js index 9d801f0c9..6afa4246b 100644 --- a/addon/components/o-s-s/anchor.stories.js +++ b/addon/components/o-s-s/anchor.stories.js @@ -24,6 +24,16 @@ export default { }, control: { type: 'text' } }, + disableAutoActive: { + description: 'Disables automatic active state based on current route', + table: { + type: { summary: 'boolean' }, + defaultValue: { summary: 'false' } + }, + control: { + type: 'boolean' + } + }, noreferrer: { description: 'Enables the noreferrer rel attribute', table: { @@ -66,6 +76,7 @@ const DefaultUsageTemplate = (args) => ({ export const BasicUsage = DefaultUsageTemplate.bind({}); BasicUsage.args = { link: 'https://www.upfluence.com', + disableAutoActive: false, noopener: true, noreferrer: true }; diff --git a/addon/components/o-s-s/anchor.ts b/addon/components/o-s-s/anchor.ts index 9e75f97ca..b4ccdf66a 100644 --- a/addon/components/o-s-s/anchor.ts +++ b/addon/components/o-s-s/anchor.ts @@ -7,6 +7,7 @@ interface OSSAnchorArgs { routePrefix?: string; noopener?: boolean; noreferrer?: boolean; + disableAutoActive?: boolean; } export default class OSSAnchor extends Component { @@ -20,6 +21,14 @@ export default class OSSAnchor extends Component { return this.args.noreferrer ?? true; } + get currentWhen(): boolean | undefined { + if (this.args.disableAutoActive !== undefined) { + return !this.args.disableAutoActive; + } + + return undefined; + } + get rel(): string { const relations: string[] = []; diff --git a/addon/components/o-s-s/layout/sidebar/group.hbs b/addon/components/o-s-s/layout/sidebar/group.hbs index bd4c4538a..fb0f9f8f4 100644 --- a/addon/components/o-s-s/layout/sidebar/group.hbs +++ b/addon/components/o-s-s/layout/sidebar/group.hbs @@ -62,6 +62,7 @@ @routePrefix={{item.routePrefix}} @lockedAction={{item.lockedAction}} @action={{item.action}} + @disableAutoActive={{item.disableAutoActive}} data-control-name={{item.dataControlName}} class={{if item.active "active"}} /> diff --git a/addon/components/o-s-s/layout/sidebar/group.ts b/addon/components/o-s-s/layout/sidebar/group.ts index f63bddaa3..d02295dc0 100644 --- a/addon/components/o-s-s/layout/sidebar/group.ts +++ b/addon/components/o-s-s/layout/sidebar/group.ts @@ -12,6 +12,7 @@ export type GroupItem = { routePrefix?: string; active: boolean; dataControlName?: string; + disableAutoActive?: boolean; lockedAction?(): unknown; action?(): void; }; diff --git a/addon/components/o-s-s/layout/sidebar/item.hbs b/addon/components/o-s-s/layout/sidebar/item.hbs index 8d9ba535e..c4ee48e28 100644 --- a/addon/components/o-s-s/layout/sidebar/item.hbs +++ b/addon/components/o-s-s/layout/sidebar/item.hbs @@ -1,6 +1,7 @@ ; expanded?: boolean; label?: string; + disableAutoActive?: boolean; lockedAction?(): void; action?(): void; } diff --git a/tests/integration/components/o-s-s/anchor-test.ts b/tests/integration/components/o-s-s/anchor-test.ts index 037e0da4f..8d89ccc61 100644 --- a/tests/integration/components/o-s-s/anchor-test.ts +++ b/tests/integration/components/o-s-s/anchor-test.ts @@ -20,21 +20,37 @@ module('Integration | Component | o-s-s/anchor', function (hooks) { assert.dom('a').hasAttribute('href', 'http://www.google.fr'); }); - test('When routePrefix is defined, it renders as a linkTo helper', async function (assert) { - const urlForStub = sinon.stub(this.router, 'urlFor').returns('/display'); - await render(hbs`test`); - - assert.ok(urlForStub.calledWithExactly('display.index')); - assert.dom('a').hasClass('ember-view'); - assert.dom('a').hasAttribute('href', '/'); - }); - - test('When link is registered in router it renders as a linkTo helper', async function (assert) { - const urlForSpy = sinon.spy(this.router, 'urlFor'); - await render(hbs`test`); - - assert.ok(urlForSpy.calledWithExactly('index')); - assert.dom('a').hasClass('ember-view'); - assert.dom('a').hasAttribute('href', '/'); + module('When link is registered in router', function () { + test('When routePrefix is defined, it renders as a linkTo helper', async function (assert) { + const urlForStub = sinon.stub(this.router, 'urlFor').returns('/display'); + await render(hbs`test`); + + assert.ok(urlForStub.calledWithExactly('display.index')); + assert.dom('a').hasClass('ember-view'); + assert.dom('a').hasAttribute('href', '/'); + }); + + test('It renders as a linkTo helper', async function (assert) { + const urlForSpy = sinon.spy(this.router, 'urlFor'); + await render(hbs`test`); + + assert.ok(urlForSpy.calledWithExactly('index')); + assert.dom('a').hasClass('ember-view'); + assert.dom('a').hasAttribute('href', '/'); + }); + + test('It renders as active by default', async function (assert) { + await render(hbs`test`); + + assert.dom('a').hasClass('ember-view'); + assert.dom('a').hasClass('active'); + }); + + test('When disableAutoActive is true, it does not render as active', async function (assert) { + await render(hbs`test`); + + assert.dom('a').hasClass('ember-view'); + assert.dom('a').doesNotHaveClass('active'); + }); }); }); From 06cefe2c1cad0dfc08faab9b6e155a0200566efe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elodie=20Labb=C3=A9?= Date: Fri, 23 Jan 2026 16:24:17 +0100 Subject: [PATCH 11/13] fix tests --- .../components/o-s-s/anchor-test.ts | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/tests/integration/components/o-s-s/anchor-test.ts b/tests/integration/components/o-s-s/anchor-test.ts index 8d89ccc61..59d70562e 100644 --- a/tests/integration/components/o-s-s/anchor-test.ts +++ b/tests/integration/components/o-s-s/anchor-test.ts @@ -3,6 +3,13 @@ import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import sinon from 'sinon'; +import Service from '@ember/service'; + +class RoutingMock extends Service { + currentState = 'index'; + generateURL = sinon.stub().returns('/'); + isActiveForRoute = sinon.stub(); +} module('Integration | Component | o-s-s/anchor', function (hooks) { setupRenderingTest(hooks); @@ -20,7 +27,12 @@ module('Integration | Component | o-s-s/anchor', function (hooks) { assert.dom('a').hasAttribute('href', 'http://www.google.fr'); }); - module('When link is registered in router', function () { + module('When link is registered in router', function (hooks) { + hooks.beforeEach(function () { + this.owner.register('service:-routing', RoutingMock); + this.routing = this.owner.lookup('service:-routing'); + }); + test('When routePrefix is defined, it renders as a linkTo helper', async function (assert) { const urlForStub = sinon.stub(this.router, 'urlFor').returns('/display'); await render(hbs`test`); @@ -39,15 +51,18 @@ module('Integration | Component | o-s-s/anchor', function (hooks) { assert.dom('a').hasAttribute('href', '/'); }); - test('It renders as active by default', async function (assert) { + test('It renders as active by default when on the correct route', async function (assert) { + this.routing.isActiveForRoute.returns(true); await render(hbs`test`); + console.log(this.routing.currentState); assert.dom('a').hasClass('ember-view'); assert.dom('a').hasClass('active'); }); - test('When disableAutoActive is true, it does not render as active', async function (assert) { - await render(hbs`test`); + test('When disableAutoActive is true and the route matches, it does not render as active', async function (assert) { + this.routing.isActiveForRoute.returns(false); + await render(hbs`test`); assert.dom('a').hasClass('ember-view'); assert.dom('a').doesNotHaveClass('active'); From 03c2ab63a714f22a3ae4ee5f23792b316ac13840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elodie=20Labb=C3=A9?= Date: Tue, 27 Jan 2026 10:53:47 +0100 Subject: [PATCH 12/13] code cleanup --- addon/components/o-s-s/anchor.hbs | 2 +- addon/components/o-s-s/anchor.ts | 6 +----- addon/components/o-s-s/layout/sidebar/item.stories.js | 2 +- tests/integration/components/o-s-s/anchor-test.ts | 5 ++--- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/addon/components/o-s-s/anchor.hbs b/addon/components/o-s-s/anchor.hbs index 4bdb4f7e9..8a72c6e67 100644 --- a/addon/components/o-s-s/anchor.hbs +++ b/addon/components/o-s-s/anchor.hbs @@ -1,5 +1,5 @@ {{#if this.isInternalRoute}} - {{yield}} + {{yield}} {{else}} {{yield}} {{/if}} \ No newline at end of file diff --git a/addon/components/o-s-s/anchor.ts b/addon/components/o-s-s/anchor.ts index b4ccdf66a..c58b21df8 100644 --- a/addon/components/o-s-s/anchor.ts +++ b/addon/components/o-s-s/anchor.ts @@ -22,11 +22,7 @@ export default class OSSAnchor extends Component { } get currentWhen(): boolean | undefined { - if (this.args.disableAutoActive !== undefined) { - return !this.args.disableAutoActive; - } - - return undefined; + return this.args.disableAutoActive !== undefined ? !this.args.disableAutoActive : undefined; } get rel(): string { diff --git a/addon/components/o-s-s/layout/sidebar/item.stories.js b/addon/components/o-s-s/layout/sidebar/item.stories.js index 45c6d5123..56fd9adcc 100644 --- a/addon/components/o-s-s/layout/sidebar/item.stories.js +++ b/addon/components/o-s-s/layout/sidebar/item.stories.js @@ -66,7 +66,7 @@ export default { } }, disableAutoActive: { - description: 'Disables automatic active state based on current route', + description: 'Disables automatic active state based on current route, managed by Ember by default', table: { type: { summary: 'boolean' }, defaultValue: { summary: 'false' } diff --git a/tests/integration/components/o-s-s/anchor-test.ts b/tests/integration/components/o-s-s/anchor-test.ts index 59d70562e..262b48143 100644 --- a/tests/integration/components/o-s-s/anchor-test.ts +++ b/tests/integration/components/o-s-s/anchor-test.ts @@ -33,7 +33,7 @@ module('Integration | Component | o-s-s/anchor', function (hooks) { this.routing = this.owner.lookup('service:-routing'); }); - test('When routePrefix is defined, it renders as a linkTo helper', async function (assert) { + test('When routePrefix is defined and an internal route is given, it renders as a linkTo helper', async function (assert) { const urlForStub = sinon.stub(this.router, 'urlFor').returns('/display'); await render(hbs`test`); @@ -42,7 +42,7 @@ module('Integration | Component | o-s-s/anchor', function (hooks) { assert.dom('a').hasAttribute('href', '/'); }); - test('It renders as a linkTo helper', async function (assert) { + test('With an internal route, it renders as a linkTo helper', async function (assert) { const urlForSpy = sinon.spy(this.router, 'urlFor'); await render(hbs`test`); @@ -54,7 +54,6 @@ module('Integration | Component | o-s-s/anchor', function (hooks) { test('It renders as active by default when on the correct route', async function (assert) { this.routing.isActiveForRoute.returns(true); await render(hbs`test`); - console.log(this.routing.currentState); assert.dom('a').hasClass('ember-view'); assert.dom('a').hasClass('active'); From 695be67080fb9344305a71cba3fedad5fd8428f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elodie=20Labb=C3=A9?= Date: Thu, 29 Jan 2026 10:43:12 +0100 Subject: [PATCH 13/13] fix current-when usage --- addon/components/o-s-s/anchor.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/components/o-s-s/anchor.hbs b/addon/components/o-s-s/anchor.hbs index 8a72c6e67..0d1afd4c7 100644 --- a/addon/components/o-s-s/anchor.hbs +++ b/addon/components/o-s-s/anchor.hbs @@ -1,5 +1,5 @@ {{#if this.isInternalRoute}} - {{yield}} + {{yield}} {{else}} {{yield}} {{/if}} \ No newline at end of file