From 6e954b5115f7b67c18daf0e8309f9db652e11c40 Mon Sep 17 00:00:00 2001 From: Radoslav Karaivanov Date: Mon, 18 Oct 2021 10:13:00 +0300 Subject: [PATCH 1/4] feat: Rating PoC --- index.ts | 1 + src/components/rating/rating.ts | 126 ++++++++++++++++++++++++++++++++ stories/rating.stories.ts | 104 ++++++++++++++++++++++++++ 3 files changed, 231 insertions(+) create mode 100644 src/components/rating/rating.ts create mode 100644 stories/rating.stories.ts diff --git a/index.ts b/index.ts index 41960c851..f604cffff 100644 --- a/index.ts +++ b/index.ts @@ -25,6 +25,7 @@ export { default as IgcNavDrawerItemComponent } from './src/components/nav-drawe export { default as IgcRadioComponent } from './src/components/radio/radio'; export { default as IgcRadioGroupComponent } from './src/components/radio-group/radio-group'; export { default as IgcRippleComponent } from './src/components/ripple/ripple'; +export { default as igcRatingComponent } from './src/components/rating/rating'; export { default as IgcSwitchComponent } from './src/components/checkbox/switch'; // utility stuff diff --git a/src/components/rating/rating.ts b/src/components/rating/rating.ts new file mode 100644 index 000000000..19c7e60ba --- /dev/null +++ b/src/components/rating/rating.ts @@ -0,0 +1,126 @@ +import { html, LitElement } from 'lit'; +import { customElement, property, queryAll, state } from 'lit/decorators.js'; +import { ifDefined } from 'lit/directives/if-defined.js'; +import { Constructor } from '../common/mixins/constructor'; +import { EventEmitterMixin } from '../common/mixins/event-emitter'; +import { SizableMixin } from '../common/mixins/sizable'; +import IgcIconComponent from '../icon/icon'; + +export interface IgcRatingEventMap { + igcChange: CustomEvent; +} + +/** + * @element igc-rating + * + * @fires igcChange - Emitted when the value of the control changes. + */ +@customElement('igc-rating') +export default class igcRatingComponent extends SizableMixin( + EventEmitterMixin>(LitElement) +) { + @queryAll('igc-icon') + protected icons!: NodeListOf; + + @state() + protected hoverValue = -1; + + @state() + protected hoverState = false; + + /** + * The number of icons to render + * @attr [length=5] + * */ + @property({ type: Number }) + public length = 5; + + /** The unfilled symbol/icon to use */ + @property({ type: String }) + public icon = 'coronavirus'; + + /** The filled symbol/icon to use */ + @property({ type: String }) + public filledIcon = 'diamond'; + + /** + * The current value of the component + * @attr [value=0] + */ + @property({ type: Number }) + public value = -1; + + /** Sets the disabled state of the component */ + @property({ type: Boolean, reflect: true }) + public disabled = false; + + /** Sets hover preview behaviour for the component */ + @property({ type: Boolean, reflect: true }) + public hover = false; + + /** Sets the readonly state of the component */ + @property({ type: Boolean, reflect: true }) + public readonly = false; + + public render() { + return this.hover + ? html` +
(this.hoverState = true)} + @mouseleave=${() => (this.hoverState = false)} + @mouseover=${this.handleMouseOver} + @click=${this.handleClick} + > + ${this.renderIcons()} +
+ ` + : html` +
+ ${this.renderIcons()} +
+ `; + } + + protected *renderIcons() { + for (let i = 0; i < this.length; i++) { + yield html``; + } + } + + protected handleClick(event: MouseEvent) { + if (this.isIconElement(event.target) && !this.readonly) { + this.value = [...this.icons].indexOf(event.target) + 1; + this.emitEvent('igcChange'); + } + } + + protected handleMouseOver(event: MouseEvent) { + if (this.isIconElement(event.target) && !this.readonly) { + this.hoverValue = [...this.icons].indexOf(event.target) + 1; + } + } + + private bindValue(index: number) { + const value = this.hoverState ? this.hoverValue : this.value; + return index < value ? this.filledIcon : this.icon; + } + + private isIconElement(el: any): el is IgcIconComponent { + return el.tagName.toLowerCase() === 'igc-icon'; + } +} diff --git a/stories/rating.stories.ts b/stories/rating.stories.ts new file mode 100644 index 000000000..6e183fbca --- /dev/null +++ b/stories/rating.stories.ts @@ -0,0 +1,104 @@ +import { html } from 'lit-html'; +import '../index.js'; +import { Context, Story } from './story.js'; +import { ifDefined } from 'lit/directives/if-defined.js'; + +// region default +const metadata = { + title: 'Rating', + component: 'igc-rating', + argTypes: { + length: { + type: 'number', + description: 'The number of icons to render', + control: 'number', + defaultValue: '5', + }, + icon: { + type: 'string', + description: 'The unfilled symbol/icon to use', + control: 'text', + defaultValue: 'coronavirus', + }, + filledIcon: { + type: 'string', + description: 'The filled symbol/icon to use', + control: 'text', + defaultValue: 'diamond', + }, + value: { + type: 'number', + description: 'The current value of the component', + control: 'number', + defaultValue: '-1', + }, + disabled: { + type: 'boolean', + description: 'Sets the disabled state of the component', + control: 'boolean', + defaultValue: false, + }, + hover: { + type: 'boolean', + description: 'Sets hover preview behaviour for the component', + control: 'boolean', + defaultValue: false, + }, + readonly: { + type: 'boolean', + description: 'Sets the readonly state of the component', + control: 'boolean', + defaultValue: false, + }, + size: { + type: '"small" | "medium" | "large"', + description: 'Determines the size of the component.', + options: ['small', 'medium', 'large'], + control: { + type: 'inline-radio', + }, + defaultValue: 'large', + }, + }, +}; +export default metadata; +interface ArgTypes { + length: number; + icon: string; + filledIcon: string; + value: number; + disabled: boolean; + hover: boolean; + readonly: boolean; + size: 'small' | 'medium' | 'large'; +} +// endregion + +const Template: Story = ( + { + size, + hover, + icon, + filledIcon, + length, + disabled, + readonly, + value, + }: ArgTypes, + { globals: { direction } }: Context +) => { + return html` + `; +}; + +export const Basic = Template.bind({}); From 5d9890c4ab8db993d0c44faa06f6a2aabe239992 Mon Sep 17 00:00:00 2001 From: Radoslav Karaivanov Date: Wed, 17 Nov 2021 12:46:43 +0200 Subject: [PATCH 2/4] feat: Impleneted keyboard navigation Added attributes needed for ARIA and standard form handling Component is following new export package structure --- .../common/definitions/defineAllComponents.ts | 2 + src/components/rating/rating.ts | 76 ++++++++++++++++--- src/index.ts | 1 + stories/rating.stories.ts | 15 +++- 4 files changed, 79 insertions(+), 15 deletions(-) diff --git a/src/components/common/definitions/defineAllComponents.ts b/src/components/common/definitions/defineAllComponents.ts index 8159345bd..a8fa91c07 100644 --- a/src/components/common/definitions/defineAllComponents.ts +++ b/src/components/common/definitions/defineAllComponents.ts @@ -22,6 +22,7 @@ import IgcNavDrawerItemComponent from '../../nav-drawer/nav-drawer-item'; import IgcNavbarComponent from '../../navbar/navbar'; import IgcRadioGroupComponent from '../../radio-group/radio-group'; import IgcRadioComponent from '../../radio/radio'; +import igcRatingComponent from '../../rating/rating'; import IgcRippleComponent from '../../ripple/ripple'; import { defineComponents } from './defineComponents'; @@ -50,6 +51,7 @@ const allComponents: CustomElementConstructor[] = [ IgcNavbarComponent, IgcRadioComponent, IgcRadioGroupComponent, + igcRatingComponent, IgcRippleComponent, ]; diff --git a/src/components/rating/rating.ts b/src/components/rating/rating.ts index 19c7e60ba..c2bbf0ec1 100644 --- a/src/components/rating/rating.ts +++ b/src/components/rating/rating.ts @@ -1,5 +1,5 @@ import { html, LitElement } from 'lit'; -import { customElement, property, queryAll, state } from 'lit/decorators.js'; +import { property, queryAll, state } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { Constructor } from '../common/mixins/constructor'; import { EventEmitterMixin } from '../common/mixins/event-emitter'; @@ -7,7 +7,7 @@ import { SizableMixin } from '../common/mixins/sizable'; import IgcIconComponent from '../icon/icon'; export interface IgcRatingEventMap { - igcChange: CustomEvent; + igcChange: CustomEvent; } /** @@ -15,10 +15,12 @@ export interface IgcRatingEventMap { * * @fires igcChange - Emitted when the value of the control changes. */ -@customElement('igc-rating') export default class igcRatingComponent extends SizableMixin( EventEmitterMixin>(LitElement) ) { + /** @private */ + public static tagName = 'igc-rating'; + @queryAll('igc-icon') protected icons!: NodeListOf; @@ -28,6 +30,15 @@ export default class igcRatingComponent extends SizableMixin( @state() protected hoverState = false; + protected navigationKeys = new Set([ + 'ArrowUp', + 'ArrowDown', + 'ArrowLeft', + 'ArrowRight', + 'Home', + 'End', + ]); + /** * The number of icons to render * @attr [length=5] @@ -36,12 +47,20 @@ export default class igcRatingComponent extends SizableMixin( public length = 5; /** The unfilled symbol/icon to use */ - @property({ type: String }) - public icon = 'coronavirus'; + @property() + public icon = 'dollar-circled'; /** The filled symbol/icon to use */ - @property({ type: String }) - public filledIcon = 'diamond'; + @property() + public filledIcon = 'apple'; + + /** The name attribute of the control */ + @property() + public name!: string; + + /** The label of the control. */ + @property() + public label!: string; /** * The current value of the component @@ -54,7 +73,7 @@ export default class igcRatingComponent extends SizableMixin( @property({ type: Boolean, reflect: true }) public disabled = false; - /** Sets hover preview behaviour for the component */ + /** Sets hover preview behavior for the component */ @property({ type: Boolean, reflect: true }) public hover = false; @@ -68,12 +87,14 @@ export default class igcRatingComponent extends SizableMixin(
(this.hoverState = true)} @mouseleave=${() => (this.hoverState = false)} @mouseover=${this.handleMouseOver} + @keydown=${this.handleKeyDown} @click=${this.handleClick} > ${this.renderIcons()} @@ -83,9 +104,11 @@ export default class igcRatingComponent extends SizableMixin(
${this.renderIcons()} @@ -103,18 +126,43 @@ export default class igcRatingComponent extends SizableMixin( } protected handleClick(event: MouseEvent) { - if (this.isIconElement(event.target) && !this.readonly) { + if (this.isIconElement(event.target) && !(this.readonly || this.disabled)) { this.value = [...this.icons].indexOf(event.target) + 1; - this.emitEvent('igcChange'); + this.emitEvent('igcChange', { detail: this.value }); } } protected handleMouseOver(event: MouseEvent) { - if (this.isIconElement(event.target) && !this.readonly) { + if (this.isIconElement(event.target) && !(this.readonly || this.disabled)) { this.hoverValue = [...this.icons].indexOf(event.target) + 1; } } + protected handleKeyDown(event: KeyboardEvent) { + if (!this.navigationKeys.has(event.key)) { + return; + } + switch (event.key) { + case 'ArrowUp': + case 'ArrowRight': + this.value += 1; + break; + case 'ArrowDown': + case 'ArrowLeft': + this.value -= 1; + break; + case 'Home': + this.value = 1; + break; + case 'End': + this.value = this.length; + break; + default: + return; + } + this.emitEvent('igcChange', { detail: this.value }); + } + private bindValue(index: number) { const value = this.hoverState ? this.hoverValue : this.value; return index < value ? this.filledIcon : this.icon; @@ -124,3 +172,9 @@ export default class igcRatingComponent extends SizableMixin( return el.tagName.toLowerCase() === 'igc-icon'; } } + +declare global { + interface HTMLElementTagNameMap { + 'igc-rating': igcRatingComponent; + } +} diff --git a/src/index.ts b/src/index.ts index 5eccfd877..babc9e225 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,6 +23,7 @@ export { default as IgcNavDrawerHeaderItemComponent } from './components/nav-dra export { default as IgcNavDrawerItemComponent } from './components/nav-drawer/nav-drawer-item'; export { default as IgcRadioComponent } from './components/radio/radio'; export { default as IgcRadioGroupComponent } from './components/radio-group/radio-group'; +export { default as igcRatingComponent } from './components/rating/rating'; export { default as IgcRippleComponent } from './components/ripple/ripple'; export { default as IgcSwitchComponent } from './components/checkbox/switch'; diff --git a/stories/rating.stories.ts b/stories/rating.stories.ts index 6e183fbca..c49d1d08a 100644 --- a/stories/rating.stories.ts +++ b/stories/rating.stories.ts @@ -1,5 +1,4 @@ import { html } from 'lit-html'; -import '../index.js'; import { Context, Story } from './story.js'; import { ifDefined } from 'lit/directives/if-defined.js'; @@ -18,13 +17,18 @@ const metadata = { type: 'string', description: 'The unfilled symbol/icon to use', control: 'text', - defaultValue: 'coronavirus', + defaultValue: 'dollar-circled', }, filledIcon: { type: 'string', description: 'The filled symbol/icon to use', control: 'text', - defaultValue: 'diamond', + defaultValue: 'apple', + }, + label: { + type: 'string', + description: 'The label of the control.', + control: 'text', }, value: { type: 'number', @@ -40,7 +44,7 @@ const metadata = { }, hover: { type: 'boolean', - description: 'Sets hover preview behaviour for the component', + description: 'Sets hover preview behavior for the component', control: 'boolean', defaultValue: false, }, @@ -66,6 +70,7 @@ interface ArgTypes { length: number; icon: string; filledIcon: string; + label: string; value: number; disabled: boolean; hover: boolean; @@ -83,11 +88,13 @@ const Template: Story = ( length, disabled, readonly, + label, value, }: ArgTypes, { globals: { direction } }: Context ) => { return html` Date: Thu, 18 Nov 2021 17:56:30 +0200 Subject: [PATCH 3/4] feat: Rating improvements * Selecting the same value will reset the rating * Rated/unrated symbols accept a callback function that can resolve the symbol for the appropriate position * Added event on hover which returns the current "active" index --- scripts/build-stories.js | 12 ++++++ src/components/rating/rating.ts | 34 ++++++++++++++--- stories/rating.stories.ts | 67 +++++++++++++++++++++++++-------- 3 files changed, 92 insertions(+), 21 deletions(-) diff --git a/scripts/build-stories.js b/scripts/build-stories.js index 6edcfa089..1e3c9c76b 100644 --- a/scripts/build-stories.js +++ b/scripts/build-stories.js @@ -70,6 +70,18 @@ function extractTags(meta) { return { component: meta.name, args: Array.from(meta.properties || []) + // Strip function types from the storybook generation + // TODO: Revise this whole pipeline and refactor it as it becomes unwieldy + .map((prop) => { + if (prop.type.includes('(')) { + prop.type = prop.type.split('|') + .map(part => part.trim()) + .filter(part => !part.startsWith('(')) + .join(' | '); + return prop; + } + return prop; + }) .filter( (prop) => SUPPORTED_TYPES.includes(prop.type) || diff --git a/src/components/rating/rating.ts b/src/components/rating/rating.ts index c2bbf0ec1..38c9b8c44 100644 --- a/src/components/rating/rating.ts +++ b/src/components/rating/rating.ts @@ -8,6 +8,7 @@ import IgcIconComponent from '../icon/icon'; export interface IgcRatingEventMap { igcChange: CustomEvent; + igcHover: CustomEvent; } /** @@ -46,13 +47,21 @@ export default class igcRatingComponent extends SizableMixin( @property({ type: Number }) public length = 5; - /** The unfilled symbol/icon to use */ + /** + * The unfilled symbol/icon to use. + * Additionally it accepts a callback function which accepts the current position + * index so the symbol can be resolved per position. + */ @property() - public icon = 'dollar-circled'; + public icon: string | ((index: number) => string) = 'dollar-circled'; - /** The filled symbol/icon to use */ + /** + * The filled symbol/icon to use. + * Additionally it accepts a callback function which accepts the current position + * index so the symbol can be resolved per position. + */ @property() - public filledIcon = 'apple'; + public filledIcon: string | ((index: number) => string) = 'apple'; /** The name attribute of the control */ @property() @@ -127,7 +136,12 @@ export default class igcRatingComponent extends SizableMixin( protected handleClick(event: MouseEvent) { if (this.isIconElement(event.target) && !(this.readonly || this.disabled)) { - this.value = [...this.icons].indexOf(event.target) + 1; + const index = [...this.icons].indexOf(event.target) + 1; + if (index === this.value) { + this.value = 0; + } else { + this.value = index; + } this.emitEvent('igcChange', { detail: this.value }); } } @@ -135,6 +149,7 @@ export default class igcRatingComponent extends SizableMixin( protected handleMouseOver(event: MouseEvent) { if (this.isIconElement(event.target) && !(this.readonly || this.disabled)) { this.hoverValue = [...this.icons].indexOf(event.target) + 1; + this.emitEvent('igcHover', { detail: this.hoverValue }); } } @@ -165,7 +180,14 @@ export default class igcRatingComponent extends SizableMixin( private bindValue(index: number) { const value = this.hoverState ? this.hoverValue : this.value; - return index < value ? this.filledIcon : this.icon; + return index < value + ? this.renderIcon(index, 'rated') + : this.renderIcon(index, 'not-rated'); + } + + private renderIcon(index: number, state: 'rated' | 'not-rated') { + const symbol = state === 'rated' ? this.filledIcon : this.icon; + return typeof symbol === 'function' ? symbol(index) : symbol; } private isIconElement(el: any): el is IgcIconComponent { diff --git a/stories/rating.stories.ts b/stories/rating.stories.ts index c49d1d08a..f7108df08 100644 --- a/stories/rating.stories.ts +++ b/stories/rating.stories.ts @@ -15,16 +15,23 @@ const metadata = { }, icon: { type: 'string', - description: 'The unfilled symbol/icon to use', + description: + 'The unfilled symbol/icon to use.\nAdditionally it accepts a callback function which accepts the current position\nindex so the symbol can be resolved per position.', control: 'text', defaultValue: 'dollar-circled', }, filledIcon: { type: 'string', - description: 'The filled symbol/icon to use', + description: + 'The filled symbol/icon to use.\nAdditionally it accepts a callback function which accepts the current position\nindex so the symbol can be resolved per position.', control: 'text', defaultValue: 'apple', }, + name: { + type: 'string', + description: 'The name attribute of the control', + control: 'text', + }, label: { type: 'string', description: 'The label of the control.', @@ -70,6 +77,7 @@ interface ArgTypes { length: number; icon: string; filledIcon: string; + name: string; label: string; value: number; disabled: boolean; @@ -79,6 +87,12 @@ interface ArgTypes { } // endregion +(metadata as any).parameters = { + actions: { + handles: ['igcChange', 'igcHover'], + }, +}; + const Template: Story = ( { size, @@ -93,19 +107,42 @@ const Template: Story = ( }: ArgTypes, { globals: { direction } }: Context ) => { - return html` - `; + const unfilled = (index: number) => { + switch (index) { + case 0: + return 'coronavirus'; + case 1: + return 'atm'; + case 2: + return 'biking'; + case 3: + return 'award'; + case 4: + return 'bacteria'; + default: + return 'dollar-circled'; + } + }; + + return html` + + +
+ If you set an empty string for the icon attribute the callback + for unrated symbols will take over. +
+ `; }; export const Basic = Template.bind({}); From 8057eb26f4cf4e0b1bb83a48fe2d2e81d6ef43d5 Mon Sep 17 00:00:00 2001 From: Radoslav Karaivanov Date: Fri, 19 Nov 2021 09:12:54 +0200 Subject: [PATCH 4/4] test: Initial rating tests --- src/components/rating/rating.spec.ts | 162 +++++++++++++++++++++++++++ src/index.ts | 2 +- 2 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 src/components/rating/rating.spec.ts diff --git a/src/components/rating/rating.spec.ts b/src/components/rating/rating.spec.ts new file mode 100644 index 000000000..9204d22f5 --- /dev/null +++ b/src/components/rating/rating.spec.ts @@ -0,0 +1,162 @@ +import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; +import sinon from 'sinon'; +import { defineComponents, IgcRatingComponent } from '../../index.js'; + +describe('Rating component', () => { + before(() => { + defineComponents(IgcRatingComponent); + }); + + const getRatingSymbols = (el: IgcRatingComponent) => + el.shadowRoot!.querySelectorAll('igc-icon'); + const getRatingWrapper = (el: IgcRatingComponent) => + el.shadowRoot!.querySelector('div') as HTMLElement; + let el: IgcRatingComponent; + + describe('', () => { + beforeEach(async () => { + el = await fixture(html``); + }); + + it('is initialized with the proper default values', async () => { + expect(el.size).to.equal('large'); + expect(el.length).to.equal(5); + expect(el.hasAttribute('disabled')).to.be.false; + expect(el.hasAttribute('hover')).to.be.false; + expect(el.hasAttribute('readonly')).to.be.false; + expect(el.name).to.be.undefined; + expect(el.label).to.be.undefined; + }); + + it('is initialized correctly with passed attributes', async () => { + const value = 10, + length = 8, + name = 'rating', + label = 'Test rating', + size = 'small'; + + el = await fixture( + html`` + ); + + expect(el.value).to.equal(value); + expect(el.length).to.equal(length); + expect(el.name).to.equal(name); + expect(el.label).to.equal(label); + expect(el.size).to.equals(size); + }); + + it('has appropriately sets ARIA attributes', async () => { + const label = 'Test Rating'; + + el.label = label; + el.length = 10; + el.value = 7; + await elementUpdated(el); + + expect(getRatingWrapper(el).getAttribute('aria-labelledby')).to.equal( + label + ); + expect(getRatingWrapper(el).getAttribute('aria-valuenow')).to.equal('7'); + expect(getRatingWrapper(el).getAttribute('aria-valuemax')).to.equal('10'); + }); + + it('correctly updates value on click', async () => { + const eventSpy = sinon.spy(el, 'emitEvent'); + getRatingSymbols(el).item(2).click(); + expect(eventSpy).calledOnceWithExactly('igcChange', { detail: 3 }); + }); + + it('correctly reflects hover state', async () => { + const eventSpy = sinon.spy(el, 'emitEvent'); + el.hover = true; + await elementUpdated(el); + + getRatingSymbols(el) + .item(2) + .dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); + expect(eventSpy).calledOnceWithExactly('igcHover', { detail: 3 }); + expect(el.value).to.equal(-1); + }); + + it('correctly resets value if the same rating value is clicked', async () => { + el.value = 5; + await elementUpdated(el); + + getRatingSymbols(el).item(4).click(); + expect(el.value).to.equal(0); + }); + + it('does nothing on click if disabled', async () => { + const eventSpy = sinon.spy(el, 'emitEvent'); + el.disabled = true; + await elementUpdated(el); + + getRatingSymbols(el).item(3).click(); + expect(eventSpy).to.not.called; + expect(el.value).to.equal(-1); + }); + + it('does nothing on click if readonly', async () => { + const eventSpy = sinon.spy(el, 'emitEvent'); + el.disabled = true; + await elementUpdated(el); + + getRatingSymbols(el).item(3).click(); + expect(eventSpy).to.not.called; + expect(el.value).to.equal(-1); + }); + + it('correctly increments rating value with arrow keys', async () => { + el.value = 3; + await elementUpdated(el); + + getRatingWrapper(el).dispatchEvent( + new KeyboardEvent('keydown', { key: 'ArrowRight' }) + ); + expect(el.value).to.equal(4); + + getRatingWrapper(el).dispatchEvent( + new KeyboardEvent('keydown', { key: 'ArrowUp' }) + ); + expect(el.value).to.equal(5); + }); + + it('correctly decrements rating value with arrow keys', async () => { + el.value = 3; + await elementUpdated(el); + + getRatingWrapper(el).dispatchEvent( + new KeyboardEvent('keydown', { key: 'ArrowLeft' }) + ); + expect(el.value).to.equal(2); + + getRatingWrapper(el).dispatchEvent( + new KeyboardEvent('keydown', { key: 'ArrowDown' }) + ); + expect(el.value).to.equal(1); + }); + + it('sets min/max rating value on Home/End keys', async () => { + el.length = 10; + el.value = 5; + await elementUpdated(el); + + getRatingWrapper(el).dispatchEvent( + new KeyboardEvent('keydown', { key: 'Home' }) + ); + expect(el.value).to.equal(1); + + getRatingWrapper(el).dispatchEvent( + new KeyboardEvent('keydown', { key: 'End' }) + ); + expect(el.value).to.equal(10); + }); + }); +}); diff --git a/src/index.ts b/src/index.ts index babc9e225..fa4b96400 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,7 +23,7 @@ export { default as IgcNavDrawerHeaderItemComponent } from './components/nav-dra export { default as IgcNavDrawerItemComponent } from './components/nav-drawer/nav-drawer-item'; export { default as IgcRadioComponent } from './components/radio/radio'; export { default as IgcRadioGroupComponent } from './components/radio-group/radio-group'; -export { default as igcRatingComponent } from './components/rating/rating'; +export { default as IgcRatingComponent } from './components/rating/rating'; export { default as IgcRippleComponent } from './components/ripple/ripple'; export { default as IgcSwitchComponent } from './components/checkbox/switch';