Skip to content

Commit 1352c8a

Browse files
aesteves60dpellier
authored andcommitted
feat(searchbar): end storybook + test
1 parent ff686d6 commit 1352c8a

File tree

12 files changed

+264
-38
lines changed

12 files changed

+264
-38
lines changed

packages/libraries/core/src/components/search-bar/ods-search-bar-events.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ import { OdsComponentEvents } from '../ods-component-events';
22

33
export interface OdsSearchBarEvents extends OdsComponentEvents {
44
/** Send event with the input value when click on button search ou with keyboard navigation */
5-
odsSearchSubmit: string
5+
odsSearchSubmit: { optionValue: string; inputValue: string },
66
}

packages/stencil/components/search-bar/src/components/osds-search-bar/osds-search-bar.e2e.screenshot.ts

+23-3
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import {
44
OdsComponentAttributes2StringAttributes,
55
odsSearchBarDefaultAttributes,
66
} from '@ovhcloud/ods-core';
7-
import { OdsCreateAttributes, OdsStringAttributes2Str, odsSearchBarBaseAttributes } from '@ovhcloud/ods-testing';
7+
import { OdsCreateAttributes, OdsStringAttributes2Str } from '@ovhcloud/ods-testing';
88

99
describe('e2e:osds-search-bar', () => {
1010
let page: E2EPage;
1111
let el: E2EElement;
1212

1313
async function setup({ attributes = {}, html = `` }: { attributes?: Partial<OdsSearchBarAttributes>, html?: string } = {}) {
14-
const minimalAttributes: OdsSearchBarAttributes = OdsCreateAttributes(attributes, odsSearchBarBaseAttributes);
14+
const minimalAttributes: OdsSearchBarAttributes = OdsCreateAttributes(attributes, odsSearchBarDefaultAttributes);
1515
const stringAttributes = OdsComponentAttributes2StringAttributes<OdsSearchBarAttributes>(minimalAttributes, odsSearchBarDefaultAttributes);
1616

1717
page = await newE2EPage();
@@ -25,6 +25,26 @@ describe('e2e:osds-search-bar', () => {
2525
}
2626

2727
describe('screenshots', () => {
28-
// Screenshot testing
28+
const options = [{ label: 'options1', value: '1' }, { label: 'options2', value: '2' }];
29+
[true, false].forEach((contrasted) => {
30+
[true, false].forEach((disabled) => {
31+
[true, false].forEach((loading) => {
32+
[undefined, [], options].forEach((options) => {
33+
[undefined, '', 'Input value'].forEach((value) => {
34+
[undefined, '', 'Placeholder'].forEach((placeholder) => {
35+
it([contrasted, disabled, loading, placeholder, options, value].join(', '), async () => {
36+
await setup({
37+
attributes: { contrasted, disabled, loading, placeholder, options, value },
38+
});
39+
await page.waitForChanges();
40+
const results = await page.compareScreenshot('seach-bar', { fullPage: false, omitBackground: true });
41+
expect(results).toMatchScreenshot({ allowableMismatchedRatio: 0 })
42+
});
43+
});
44+
});
45+
});
46+
});
47+
});
48+
});
2949
});
3050
});
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,138 @@
11
import { E2EElement, E2EPage, newE2EPage } from '@stencil/core/testing';
22
import { OdsSearchBarAttributes, OdsComponentAttributes2StringAttributes, odsSearchBarDefaultAttributes } from '@ovhcloud/ods-core';
3-
import { OdsCreateAttributes, OdsStringAttributes2Str, odsSearchBarBaseAttributes } from '@ovhcloud/ods-testing';
3+
import { OdsCreateAttributes, OdsStringAttributes2Str } from '@ovhcloud/ods-testing';
44

55
describe('e2e:osds-search-bar', () => {
66
let page: E2EPage;
77
let el: E2EElement;
8+
let select: E2EElement;
9+
let button: E2EElement;
10+
let input: E2EElement;
811

912
async function setup({ attributes }: { attributes: Partial<OdsSearchBarAttributes> }) {
10-
const minimalAttributes: OdsSearchBarAttributes = OdsCreateAttributes(attributes, odsSearchBarBaseAttributes);
13+
const minimalAttributes: OdsSearchBarAttributes = OdsCreateAttributes(attributes, odsSearchBarDefaultAttributes);
1114
const stringAttributes = OdsComponentAttributes2StringAttributes<OdsSearchBarAttributes>(minimalAttributes, odsSearchBarDefaultAttributes);
1215

1316
page = await newE2EPage();
1417
await page.setContent(`<osds-search-bar ${OdsStringAttributes2Str(stringAttributes)}></osds-search-bar>`);
1518
await page.evaluate(() => document.body.style.setProperty('margin', '0px'));
19+
await page.waitForChanges();
1620

1721
el = await page.find('osds-search-bar');
22+
el.setProperty('options', attributes.options ?? []);
23+
24+
await page.waitForChanges();
25+
26+
select = await page.find('osds-search-bar >>> osds-select');
27+
input = await page.find('osds-search-bar >>> osds-input');
28+
button = await page.find('osds-search-bar >>> osds-button');
29+
30+
await page.waitForChanges();
1831
}
1932

2033
it('should render', async () => {
2134
await setup({ attributes: {} });
2235
expect(el).not.toBeNull();
2336
expect(el).toHaveClass('hydrated');
37+
38+
expect(input).not.toBeNull();
39+
expect(input).toHaveClass('hydrated');
40+
41+
expect(button).not.toBeNull();
42+
expect(button).toHaveClass('hydrated');
43+
44+
expect(select).toBeNull();
45+
});
46+
47+
it('should get default attributes', async () => {
48+
await setup({ attributes: {} });
49+
expect(await el.getProperty('contrasted')).toBe(odsSearchBarDefaultAttributes.contrasted);
50+
expect(await el.getProperty('disabled')).toBe(odsSearchBarDefaultAttributes.disabled);
51+
expect(await el.getProperty('value')).toBe(odsSearchBarDefaultAttributes.value);
52+
expect(await el.getProperty('loading')).toBe(odsSearchBarDefaultAttributes.loading);
53+
expect(await el.getProperty('options')).toEqual(odsSearchBarDefaultAttributes.options);
54+
expect(await el.getProperty('placeholder')).toBe(odsSearchBarDefaultAttributes.placeholder);
55+
});
2456

25-
// E2E testing
57+
it('should display select because of options', async () => {
58+
const options = [{ label: 'options1', value: '1' }, { label: 'options2', value: '2' }];
59+
await setup({ attributes: { options } });
60+
61+
expect(select).not.toBeNull();
62+
expect(select).toHaveClass('hydrated');
2663
});
2764

65+
describe('Event', () => {
66+
it('should receive event odsValueChange', async () => {
67+
await setup({ attributes: { } });
68+
69+
const odsValueChange = await el.spyOnEvent('odsValueChange');
70+
71+
await input.setProperty('value', '4');
72+
await page.waitForChanges();
73+
expect(odsValueChange).toHaveReceivedEventTimes(1);
74+
});
75+
76+
it('should receive event odsSearchSubmit with current value', async () => {
77+
await setup({ attributes: { } });
78+
79+
const odsSearchSubmit = await el.spyOnEvent('odsSearchSubmit');
80+
await input.setProperty('value', '4');
81+
82+
await page.waitForChanges();
83+
await button.click();
84+
85+
expect(odsSearchSubmit).toHaveReceivedEventTimes(1);
86+
expect(odsSearchSubmit).toHaveReceivedEventDetail({ optionValue: '', inputValue: '4' });
87+
});
88+
89+
it('should receive event odsValueChange with options selected', async () => {
90+
const options = [{ label: 'options1', value: '1' }, { label: 'options2', value: '2' }];
91+
await setup({ attributes: { options } });
92+
93+
const odsValueChange = await el.spyOnEvent('odsValueChange');
94+
95+
await select.click();
96+
97+
const optionElement = await page.find('osds-search-bar >>> osds-select > osds-select-option');
98+
await optionElement.click();
99+
100+
await page.waitForChanges();
101+
expect(odsValueChange).toHaveReceivedEventTimes(1);
102+
});
103+
104+
it('should receive event odsSearchSubmit with options selected', async () => {
105+
const options = [{ label: 'options1', value: '1' }, { label: 'options2', value: '2' }];
106+
await setup({ attributes: { options } });
107+
108+
const odsSearchSubmit = await el.spyOnEvent('odsSearchSubmit');
109+
110+
await select.click();
111+
112+
const optionElement = await page.find('osds-search-bar >>> osds-select > osds-select-option');
113+
await optionElement.click();
114+
await button.click();
115+
116+
await page.waitForChanges();
117+
expect(odsSearchSubmit).toHaveReceivedEventTimes(1);
118+
expect(odsSearchSubmit).toHaveReceivedEventDetail({ optionValue: '1', inputValue: '' });
119+
});
120+
});
121+
122+
// describe.only('a11y', () => {
123+
// it('should navigate with tab', async () => {
124+
// const options = [{ label: 'options1', value: '1' }, { label: 'options2', value: '2' }];
125+
// await setup({ attributes: { options } });
126+
127+
// await page.keyboard.press('Tab');
128+
// await page.keyboard.press('Tab');
129+
130+
// const activeElement = await page.evaluateHandle(() => {
131+
// console.log('document.activeElementtagName', document.activeElement.tagName);
132+
// return document.activeElement;
133+
// });
134+
// console.log('activeElement', await (await activeElement.asElement()).getProperty('tagName'), await activeElement.getProperty('tagName'));
135+
// expect(true).toBe(await select.getProperty('tagName'));
136+
// });
137+
// })
28138
});

packages/stencil/components/search-bar/src/components/osds-search-bar/osds-search-bar.scss

+11-1
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,26 @@
77

88
:host {
99
display: inline-flex;
10-
osds-select::part(section), .first {
10+
11+
osds-select {
12+
--ods-size-select-border-radius-bottom-right: 0;
13+
--ods-size-select-border-radius-top-right: 0;
14+
--ods-size-select-border-right: none;
15+
}
16+
17+
.first {
1118
border-bottom-right-radius: 0;
1219
border-top-right-radius: 0;
1320
}
21+
1422
osds-input {
1523
flex: auto;
1624
}
25+
1726
osds-input:not(.first) {
1827
border-radius: 0;
1928
}
29+
2030
osds-button::part(button) {
2131
border-bottom-left-radius: 0;
2232
border-top-left-radius: 0;

packages/stencil/components/search-bar/src/components/osds-search-bar/osds-search-bar.spec.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import {
1010
} from '@ovhcloud/ods-testing';
1111
import { SpecPage, newSpecPage } from '@stencil/core/testing';
1212

13-
import { OdsThemeColorIntentList } from '@ovhcloud/ods-theming';
1413
import { OsdsSearchBar } from './osds-search-bar';
1514
import { getAttributeContextOptions } from '@ovhcloud/ods-stencil/libraries/stencil-testing';
1615

1716
describe('spec:osds-search-bar', () => {
1817
let page: SpecPage;
1918
let root: HTMLElement | undefined;
2019
let instance: OsdsSearchBar;
20+
let buttonSearch: HTMLElement;
2121

2222
afterEach(() => {
2323
jest.clearAllMocks();
@@ -34,6 +34,7 @@ describe('spec:osds-search-bar', () => {
3434

3535
root = page.root;
3636
instance = page.rootInstance;
37+
buttonSearch = root.shadowRoot.querySelector('osds-button');
3738
}
3839

3940
it('should render', async () => {
@@ -105,5 +106,14 @@ describe('spec:osds-search-bar', () => {
105106
});
106107
});
107108

108-
109+
describe('methods', () => {
110+
it('should call emit odsSearchSubmit', async () => {
111+
await setup({});
112+
const spyEmitOdsSearchSubmit = jest.spyOn(instance.odsSearchSubmit, 'emit');
113+
buttonSearch.click();
114+
instance.handlerOnClickSearchButton();
115+
expect(spyEmitOdsSearchSubmit).toHaveBeenCalledTimes(2);
116+
expect(spyEmitOdsSearchSubmit).toHaveBeenCalledWith({ optionValue: '', inputValue: '' });
117+
})
118+
});
109119
});

packages/stencil/components/search-bar/src/components/osds-search-bar/osds-search-bar.tsx

+29-12
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import { Component, Element, Host, h, Prop, Event, EventEmitter } from '@stencil/core';
1+
import { Component, Element, Host, h, Prop, Event, EventEmitter, Listen } from '@stencil/core';
22
import {
33
OdsButtonSize,
44
OdsIconName,
55
OdsInputType,
6+
OdsInputValueChangeEvent,
67
OdsSearchBar,
78
OdsSearchBarController,
89
odsSearchBarDefaultAttributes,
910
OdsSearchBarEvents,
1011
OdsSearchBarMethods,
12+
OdsSelectValueChangeEvent,
1113
// odsSearchBarDefaultAttributes
1214
} from '@ovhcloud/ods-core';
1315
import { OdsStencilEvents, OdsStencilMethods } from '@ovhcloud/ods-stencil/libraries/stencil-core';
@@ -23,6 +25,8 @@ import { OdsThemeColorIntent } from '@ovhcloud/ods-theming';
2325
})
2426
export class OsdsSearchBar implements OdsSearchBar<OdsStencilMethods<OdsSearchBarMethods>, OdsStencilEvents<OdsSearchBarEvents>> {
2527
controller: OdsSearchBarController = new OdsSearchBarController(this);
28+
private optionValue: string = '';
29+
2630
@Element() el!: HTMLElement;
2731

2832
/** @see OdsSearchBarAttributes.contrasted */
@@ -41,46 +45,59 @@ export class OsdsSearchBar implements OdsSearchBar<OdsStencilMethods<OdsSearchBa
4145
@Prop({ reflect: true }) public options?: { label: string; value: string; }[] = odsSearchBarDefaultAttributes.options;
4246

4347
/** @see OdsSearchBarAttributes.value */
44-
@Prop({ reflect: true }) public value: string = odsSearchBarDefaultAttributes.value;
48+
@Prop({ reflect: true, mutable: true }) public value: string = odsSearchBarDefaultAttributes.value;
4549

4650
/** @see OdsSearchBarEvents.odsSearchSubmit */
47-
@Event() odsSearchSubmit!: EventEmitter<string>;
51+
@Event() odsSearchSubmit!: EventEmitter<{ optionValue: string; inputValue: string }>;
52+
53+
@Listen('odsValueChange')
54+
onValueChange(event: OdsInputValueChangeEvent | OdsSelectValueChangeEvent): void {
55+
const isOdsSelectValueChange = 'selection' in event.detail;
56+
if (isOdsSelectValueChange) {
57+
this.optionValue = event.detail.value?.toString() ?? '';
58+
return;
59+
}
60+
this.value = event.detail.value?.toString() ?? '';
61+
}
4862

4963
handlerOnClickSearchButton(): void {
5064
this.emitSearchSubmit();
5165
}
5266

5367
emitSearchSubmit(): void {
54-
this.odsSearchSubmit.emit(this.value);
68+
this.odsSearchSubmit.emit({ optionValue: this.optionValue, inputValue: this.value });
5569
}
5670

5771
render() {
5872
const hasSelect = this.options?.length;
73+
5974
return (
6075
<Host>
6176
{
6277
hasSelect &&
63-
<osds-select class="search-bar__child" tabindex="0" disabled={ this.disabled }>
64-
{ this.options?.map((option) => {
65-
return (
66-
<osds-select-option value={ option.value }>{ option.label }</osds-select-option>)
67-
})}
78+
<osds-select
79+
tabindex="0"
80+
disabled={ this.disabled }>
81+
{ this.options?.map((option) => <osds-select-option value={ option.value }>{ option.label }</osds-select-option>) }
6882
</osds-select>
6983
|| ''
7084
}
7185

72-
<osds-input tabindex="1"
86+
<osds-input
87+
tabindex="1"
7388
color={ OdsThemeColorIntent.primary }
7489
type={ OdsInputType.text }
7590
clearable
91+
contrasted={ this.contrasted }
7692
value={ this.value }
7793
loading={ this.loading }
7894
disabled={ this.disabled }
7995
placeholder={ this.placeholder }
8096
class={{ 'first': !hasSelect }}>
8197
</osds-input>
8298

83-
<osds-button tabindex="2"
99+
<osds-button
100+
tabindex="2"
84101
onClick={ () => this.handlerOnClickSearchButton() }
85102
onKeyDown={ (event: KeyboardEvent) => {
86103
const isEnter = event.code.includes('Enter');
@@ -92,7 +109,7 @@ export class OsdsSearchBar implements OdsSearchBar<OdsStencilMethods<OdsSearchBa
92109
size={ OdsButtonSize.sm }
93110
color={ OdsThemeColorIntent.primary }
94111
disabled={ this.disabled }
95-
class="search-bar__child">
112+
contrasted={ this.contrasted }>
96113
<osds-icon contrasted name={ OdsIconName.SEARCH } />
97114
</osds-button>
98115
</Host>

packages/stencil/components/search-bar/src/components/osds-search-bar/styles/osds-search-bar.size.scss

-7
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,7 @@
44
// Main CSS mixin for sizes
55
@mixin osds-search-bar-theme-size() {
66
:host {
7-
height: 42px;
87
width: 654px;
9-
> * {
10-
height: 100%;
11-
}
12-
osds-button::part(button) {
13-
height: 42px;
14-
}
158
osds-select {
169
width: 30%;
1710
}

0 commit comments

Comments
 (0)