Skip to content

Commit

Permalink
fix(tabs): add accessibility tests
Browse files Browse the repository at this point in the history
  • Loading branch information
skhamvon authored and dpellier committed Jan 16, 2024
1 parent b44b79e commit 82b3e5d
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 96 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import type { OdsLoggerSpyReferences } from '@ovhcloud/ods-common-testing';
import type { HTMLStencilElement } from '@stencil/core/internal';

import { Ods, OdsLogger } from '@ovhcloud/ods-common-core';
import { OdsClearLoggerSpy, OdsInitializeLoggerSpy } from '@ovhcloud/ods-common-testing';


import { OdsTabsController } from './controller';
import { OsdsTabBar } from '../../osds-tab-bar/osds-tab-bar';
import { OsdsTabBarItem } from '../../osds-tab-bar-item/osds-tab-bar-item';
import { OsdsTabsPanel } from '../../osds-tab-panel/osds-tab-panel';
import { DEFAULT_ATTRIBUTE } from '../constants/default-attributes';
import { OsdsTabs } from '../osds-tabs';

class OdsTabsMock extends OsdsTabs {
Expand All @@ -24,7 +20,6 @@ class OdsTabsMock extends OsdsTabs {
}

describe('spec:ods-tabs-controller', () => {
const baseAttribute = { contrasted: DEFAULT_ATTRIBUTE.contrasted, panel: DEFAULT_ATTRIBUTE.panel, size: DEFAULT_ATTRIBUTE.size };
let controller: OdsTabsController;
let component: OsdsTabs;

Expand All @@ -48,6 +43,8 @@ describe('spec:ods-tabs-controller', () => {
value: document.createElement('osds-tabs') as HTMLStencilElement,
writable: true,
});

controller.tabItems = [item1, item2];
}

beforeEach(() => {
Expand Down Expand Up @@ -78,14 +75,6 @@ describe('spec:ods-tabs-controller', () => {
OdsClearLoggerSpy(loggerSpyReferences);
});

function mockGetTabItems(items: Array<OsdsTabBarItem & HTMLElement>) {
jest.spyOn(component, 'getTabItems').mockImplementation(() => items);
}

function mockGetTabPanels(panels: Array<OsdsTabsPanel & HTMLElement>) {
jest.spyOn(component, 'getTabPanels').mockImplementation(() => panels);
}

it('should initialize', () => {
setup({});
expect(controller).toBeTruthy();
Expand Down Expand Up @@ -113,29 +102,6 @@ describe('spec:ods-tabs-controller', () => {
});
});

describe('getTabItems', () => {
it('should retrieve items inside tab bar', () => {
setup({ panel: '2', contrasted: true });

component.el.appendChild(tabBar);

const items = controller.getTabItems('osds-tab-bar-item');
expect(items).toEqual([item1, item2]);
});
});

describe('getTabPanels', () => {
it('should retrieve panels tabs', () => {
setup({ panel: '2', contrasted: true });

component.el.appendChild(panel1);
component.el.appendChild(panel2);

const items = controller.getTabPanels('osds-tabs-panel');
expect(items).toEqual([panel1, panel2]);
});
});

describe('changeActivePanel', () => {
beforeEach(() => {
setup({ panel: '', contrasted: true });
Expand All @@ -146,8 +112,8 @@ describe('spec:ods-tabs-controller', () => {
component.el.appendChild(panel1);
component.el.appendChild(panel2);

mockGetTabItems([item1, item2]);
mockGetTabPanels([panel1, panel2]);
controller.tabItems = [item1, item2];
controller.tabPanels = [panel1, panel2];
});

describe('panel unset', () => {
Expand Down Expand Up @@ -203,10 +169,59 @@ describe('spec:ods-tabs-controller', () => {

});

describe('propagateContrastedToItems', () => {
describe('handleArrowKey', () => {
beforeEach(() => {
setup({ panel: '', contrasted: true });
});

it('should do nothing if arrowLeft is pressed on first tab', () => {
item1.setAttribute('active', '');
item2.removeAttribute('active');

const key = new KeyboardEvent('keypress', { code : 'ArrowLeft' });
controller.handleArrowKey(key);

expect(item1.getAttribute('active')).toEqual("");
expect(item2.getAttribute('active')).toEqual(null);
});

it('should do nothing if arrowRight is pressed on last tab', () => {
item1.removeAttribute('active');
item2.setAttribute('active', '');

const key = new KeyboardEvent('keypress', { code : 'ArrowRight' });
controller.handleArrowKey(key);

expect(item1.getAttribute('active')).toEqual(null);
expect(item2.getAttribute('active')).toEqual("");
});


it('should set active to tab 2', () => {
item1.setAttribute('active', '');
item2.removeAttribute('active');

const key = new KeyboardEvent('keypress', { code : 'ArrowRight' });
controller.handleArrowKey(key);

expect(item1.getAttribute('active')).toEqual("");
expect(item2.getAttribute('active')).toEqual(null);
});

it('should set active to tab 1', () => {
item1.removeAttribute('active');
item2.setAttribute('active', '');

const key = new KeyboardEvent('keypress', { code : 'ArrowLeft' });
controller.handleArrowKey(key);

expect(item2.getAttribute('active')).toEqual("");
expect(item1.getAttribute('active')).toEqual(null);
});
});

describe('propagateContrastedToItems', () => {
beforeEach(() => {
mockGetTabItems([item1, item2]);
setup(component);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,68 +1,78 @@
import type { OsdsTabBarItem } from '../../osds-tab-bar-item/osds-tab-bar-item';
import type { OsdsTabsPanel } from '../../osds-tab-panel/osds-tab-panel';
import type { OsdsTabs } from '../osds-tabs';

import { OsdsTabBarItem } from '../../osds-tab-bar-item/osds-tab-bar-item';
import { OsdsTabsPanel } from '../../osds-tab-panel/osds-tab-panel';

/**
* common controller logic for text component used by the different implementations.
* it contains all the glue between framework implementation and the third party service.
*/
class OdsTabsController {
protected component: OsdsTabs;

private _tabItems: (Array<OsdsTabBarItem & HTMLElement>) = [];

public get tabItems(): (Array<OsdsTabBarItem & HTMLElement>) {
return this._tabItems;
}

public set tabItems(value: (Array<OsdsTabBarItem & HTMLElement>)) {
this._tabItems = value;
}

private _tabPanels: (Array<OsdsTabsPanel & HTMLElement>) = [];

public get tabPanels(): (Array<OsdsTabsPanel & HTMLElement>) {
return this._tabPanels;
}

public set tabPanels(value: (Array<OsdsTabsPanel & HTMLElement>)) {
this._tabPanels = value;
}

constructor(component: OsdsTabs) {
this.component = component;
}

beforeInit() {
beforeInit(): void {
this.changeActivePanel(this.component.panel);
this.component.onContrastedPropChange(this.component.contrasted);
}

changeActivePanel(panel: string) {
const items = this.component.getTabItems();
changeActivePanel(panel: string): void {
const items = this.tabItems;
// if not item found, select the first one
if (!items.find((item) => item.panel === panel) && items.length) {
panel = items[0].panel;
}
items.forEach((item) => item.active = item.panel === panel);
this.component.getTabPanels().forEach((tabPanel) => tabPanel.active = tabPanel.name === panel);
this.tabPanels.forEach((tabPanel) => tabPanel.active = tabPanel.name === panel);
if (this.component.panel !== panel) {
this.component.panel = panel;
this.component.emitChanged();
}
}

handleArrowKey(event: KeyboardEvent): void {
const currentSelectedTabIndex = this.component.getTabItems().findIndex((tab) => tab.hasAttribute('active'))
const currentSelectedTabIndex = this.tabItems.findIndex((tab) => tab.hasAttribute('active'));
if(currentSelectedTabIndex === 0 && event.code === 'ArrowLeft' ) {
return;
}
if(currentSelectedTabIndex === this.component.getTabItems().length - 1 && event.code === 'ArrowRight' ) {
if(currentSelectedTabIndex === this.tabItems.length - 1 && event.code === 'ArrowRight' ) {
return;
}

if(event.code === 'ArrowLeft') {
const previousPanel = this.component.getTabItems()[currentSelectedTabIndex - 1].getAttribute('panel');
const previousPanel = this.tabItems[currentSelectedTabIndex - 1].getAttribute('panel');
this.changeActivePanel(previousPanel!);
} else {
const nextPanel = this.component.getTabItems()[currentSelectedTabIndex + 1].getAttribute('panel');
const nextPanel = this.tabItems[currentSelectedTabIndex + 1].getAttribute('panel');
this.changeActivePanel(nextPanel!);
}
}

propagateContrastedToItems(contrasted: boolean) {
this.component.getTabItems().forEach((item) => item.contrasted = contrasted);
}

getTabItems(elementTag: string) {
return Array.from(this.component.el.querySelectorAll<OsdsTabBarItem & HTMLElement>(elementTag));
}

getTabPanels(elementTag: string) {
return Array.from(this.component.el.querySelectorAll<OsdsTabsPanel & HTMLElement>(elementTag));
propagateContrastedToItems(contrasted: boolean): void {
this.tabItems.forEach((item) => item.contrasted = contrasted);
}

}

export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,16 @@ describe('spec:osds-tabs', () => {
expect(controller.changeActivePanel).toHaveBeenCalledWith('a');
});

it('should handle slot change', async() => {
await setup({ attributes: { } });
const spySetTabItems = jest.spyOn(instance, 'setTabItems');
const spySetTabPanels = jest.spyOn(instance, 'setTabPanels');

await instance.handleSlotChange();
expect(spySetTabItems).toHaveBeenCalled();
expect(spySetTabPanels).toHaveBeenCalled();
});

describe('emitChanged', () => {
it('should emit odsTabsChanged', async() => {
await setup({ attributes: { panel: 'a' }, html: baseHtml({}) });
Expand All @@ -161,19 +171,15 @@ describe('spec:osds-tabs', () => {
});
});

describe('getTabItems', () => {
it('should call controller.getTabItems', async() => {
describe('handleArrowKey', () => {
it('should call controller.handleArrowKey', async() => {
await setup({ attributes: {}, html: baseHtml({}) });
instance.getTabItems();
expect(controller.getTabItems).toHaveBeenCalledWith('osds-tab-bar-item');
});
});

describe('getTabPanels', () => {
it('should call controller.getTabPanels', async() => {
await setup({ attributes: {}, html: baseHtml({}) });
instance.getTabPanels();
expect(controller.getTabPanels).toHaveBeenCalledWith('osds-tab-panel');
const key = new KeyboardEvent('keypress', { code : 'ArrowLeft' });
instance.handleArrowKey(key);

expect(controller.handleArrowKey).toHaveBeenCalledTimes(1);
expect(controller.handleArrowKey).toHaveBeenCalledWith(key);
});
});

Expand Down
Loading

0 comments on commit 82b3e5d

Please sign in to comment.