diff --git a/packages/components-react/package.json b/packages/components-react/package.json index 8ce8f3d00e..a65e8fcfcc 100644 --- a/packages/components-react/package.json +++ b/packages/components-react/package.json @@ -13,9 +13,9 @@ "linkDirectory": true }, "scripts": { - "build": "npm run clean && npm run compile", + "build": "pnpm run clean && pnpm run compile", "clean": "rimraf dist", - "compile": "npm run tsc", + "compile": "pnpm run tsc", "tsc": "tsc -p ." }, "dependencies": { diff --git a/packages/components/cypress/e2e/rating.cy.ts b/packages/components/cypress/e2e/rating.cy.ts new file mode 100644 index 0000000000..9fd5d966db --- /dev/null +++ b/packages/components/cypress/e2e/rating.cy.ts @@ -0,0 +1,217 @@ +describe('rating', () => { + describe('default', () => { + beforeEach(() => { + cy.getComponent('post-rating'); + cy.get('@rating').get('div.rating').as('rating-container'); + cy.get('@rating-container').get('svg').as('stars'); + }); + + it('should render', () => { + cy.get('@rating').should('exist'); + }); + + it('should display 10 empty stars', () => { + cy.get('@stars').then($stars => { + expect($stars.length).to.equal(10); + + $stars.each((_index, star) => { + const classes = Cypress.$(star).attr('class').split(' '); + expect(classes).to.have.length(1); + expect(classes[0]).to.equal('star'); + }); + }); + }); + + it('should set correct rating by clicking on a star', () => { + for (let i = 0; i < 10; i++) { + cy.get('@stars').eq(i).click(); + cy.get('@stars').each(($star, index) => { + if (index <= i) { + cy.wrap($star).should('have.class', 'active-star'); + } else { + cy.wrap($star).should('not.have.class', 'active-star'); + } + }); + } + }); + + // The hover test does not yet work, need to find a way to make assertions while hovering + + // it('should change appearance on hover', () => { + // cy.get('@stars').eq(3).trigger('mouseover'); + // cy.get('@stars').each(($star, index) => { + // if (index <= 3) { + // cy.wrap($star).should('have.class', 'hovered-star'); + // } else { + // cy.wrap($star).should('not.have.class', 'hovered-star'); + // } + // }); + // }); + + it('should check if stars reflect current-rating attribute correctly', () => { + for (let rating = 1; rating <= 10; rating++) { + cy.get('@rating').invoke('attr', 'current-rating', `${rating}`); + + cy.get('@stars').each(($star, index) => { + if (index < rating) { + cy.wrap($star).should('have.class', 'active-star'); + } else { + cy.wrap($star).should('not.have.class', 'active-star'); + } + }); + } + }); + + it('should render the correct number of stars based on max attribute', () => { + cy.get('@rating').invoke('attr', 'max', 7); + cy.get('@stars').should('have.length', 7); + }); + + // The keyboard test does not yet work because of the focus + + // it('should navigate using keyboard arrows and confirm selection with Enter', () => { + // cy.get('@rating-container') + // .focus() + // .type('{rightarrow}{rightarrow}{rightarrow}{rightarrow}{enter}'); + // cy.get('@stars').each(($star, index) => { + // if (index < 4) { + // cy.wrap($star).should('have.class', 'active-star'); + // } else { + // cy.wrap($star).should('not.have.class', 'active-star'); + // } + // }); + // }); + + it('should disable interaction when disabled', () => { + cy.get('@rating').invoke('attr', 'disabled', true); + cy.get('@stars').each(($star, index) => { + cy.wrap($star).should('have.class', 'default-disabled'); + + cy.wrap($star) + .click() + .then(() => { + if (index === 0) { + cy.wrap($star).should('not.have.class', 'active-star'); + } else { + for (let i = 0; i <= index; i++) { + cy.get('@stars').eq(i).should('not.have.class', 'active-star'); + } + } + }); + }); + }); + + it('should maintain default active stars in disabled state', () => { + const defaultRating = 3; + cy.get('@rating').invoke('attr', 'current-rating', defaultRating); + + cy.get('@rating').invoke('attr', 'disabled', true); + + cy.get('@stars').each(($star, index) => { + if (index < defaultRating) { + cy.wrap($star).should('have.class', 'active-disabled'); + } else { + cy.wrap($star).should('have.class', 'default-disabled'); + cy.wrap($star).should('not.have.class', 'active-disabled'); + } + + cy.wrap($star) + .click() + .then(() => { + if (index < defaultRating) { + cy.wrap($star).should('have.class', 'active-disabled'); + cy.wrap($star).should('not.have.class', 'active-star'); + } else { + cy.wrap($star).should('have.class', 'default-disabled'); + cy.wrap($star).should('not.have.class', 'active-disabled'); + } + }); + }); + }); + + it('should disable interaction when readonly', () => { + cy.get('@rating').invoke('attr', 'readonly', true); + cy.get('@stars').each(($star, index) => { + const classes = Cypress.$($star).attr('class').split(' '); + expect(classes).to.have.length(1); + expect(classes[0]).to.equal('star'); + + cy.wrap($star) + .click() + .then(() => { + if (index === 0) { + cy.wrap($star).should('not.have.class', 'active-star'); + } else { + for (let i = 0; i <= index; i++) { + cy.get('@stars').eq(i).should('not.have.class', 'active-star'); + } + } + }); + }); + }); + + it('should maintain default active stars in readonly state', () => { + const defaultRating = 3; + cy.get('@rating').invoke('attr', 'current-rating', defaultRating); + + cy.get('@rating').invoke('attr', 'readonly', true); + + cy.get('@stars').each(($star, index) => { + if (index < defaultRating) { + cy.wrap($star).should('have.class', 'active-star'); + } else { + cy.wrap($star).should('not.have.class', 'active-star'); + } + + cy.wrap($star) + .click() + .then(() => { + if (index < defaultRating) { + cy.wrap($star).should('have.class', 'active-star'); + } else { + cy.wrap($star).should('not.have.class', 'active-star'); + } + }); + }); + }); + + it('should have correct ARIA attributes', () => { + const defaultRating = 3; + const max = 8; + + cy.get('@rating').invoke('attr', 'current-rating', defaultRating); + + cy.get('@rating').invoke('attr', 'disabled', true); + + cy.get('@rating').invoke('attr', 'max', max); + + // Check ARIA attributes + cy.get('@rating-container').should('have.attr', 'role', 'slider'); + cy.get('@rating-container').should('have.attr', 'aria-valuemin', '0'); + cy.get('@rating-container').should('have.attr', 'aria-valuemax', `${max}`); + cy.get('@rating-container').should('have.attr', 'aria-valuenow', `${defaultRating}`); + cy.get('@rating-container').should( + 'have.attr', + 'aria-valuetext', + `${defaultRating} out of ${max}`, + ); + cy.get('@rating-container').should('have.attr', 'aria-readonly', 'false'); + cy.get('@rating-container').should('have.attr', 'aria-disabled', 'true'); + }); + + it('should emit ratingChanged event when rating changes', () => { + cy.get('@rating').then($rating => { + const listener = cy.stub(); + $rating[0].addEventListener('ratingChanged', listener); + + const newRating = 5; + cy.get('@stars') + .eq(newRating - 1) + .click() + .then(() => { + cy.wrap(listener).should('be.calledWithMatch', { detail: newRating }); + }); + }); + }); + }); +}); diff --git a/packages/components/package.json b/packages/components/package.json index 9df337f8a8..3f441e1364 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -28,7 +28,7 @@ "start": "stencil build --watch --docs-readme", "build": "stencil build --docs-readme", "clean": "rimraf www dist loader", - "test": "npm run unit", + "test": "pnpm run unit", "unit": "stencil test --spec", "unit:watch": "stencil test --spec --watchAll --silent", "e2e": "cypress run", diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index e58b5886b8..26b787c82b 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -111,7 +111,7 @@ export namespace Components { */ "currentRating": number; /** - * The number of stars in the rating + * Boolean for the disabled state of the component */ "disabled"?: boolean; /** @@ -119,7 +119,7 @@ export namespace Components { */ "max"?: number; /** - * The number of stars in the rating + * If readonly is true, the component only displays a rating and is not interactive. */ "readonly"?: boolean; } @@ -387,7 +387,7 @@ declare namespace LocalJSX { */ "currentRating"?: number; /** - * The number of stars in the rating + * Boolean for the disabled state of the component */ "disabled"?: boolean; /** @@ -399,7 +399,7 @@ declare namespace LocalJSX { */ "onRatingChanged"?: (event: PostRatingCustomEvent) => void; /** - * The number of stars in the rating + * If readonly is true, the component only displays a rating and is not interactive. */ "readonly"?: boolean; } diff --git a/packages/components/src/components/post-rating/post-rating.tsx b/packages/components/src/components/post-rating/post-rating.tsx index a7d9f54f42..d339cc1a19 100644 --- a/packages/components/src/components/post-rating/post-rating.tsx +++ b/packages/components/src/components/post-rating/post-rating.tsx @@ -148,7 +148,7 @@ export class PostRating { onClick={this.isInteractive() && (() => this.handleClick(i))} onMouseEnter={this.isInteractive() && (() => this.handleHover(i))} onMouseLeave={this.isInteractive() && (() => this.reset())} - cursor={this.isInteractive() && 'pointer'} + cursor={this.isInteractive() ? 'pointer' : 'default'} > , diff --git a/packages/components/src/components/post-rating/readme.md b/packages/components/src/components/post-rating/readme.md index 2d6da9f138..c172ed73ae 100644 --- a/packages/components/src/components/post-rating/readme.md +++ b/packages/components/src/components/post-rating/readme.md @@ -7,12 +7,12 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| --------------- | ---------------- | --------------------------------- | --------- | ----------- | -| `currentRating` | `current-rating` | The current rating value | `number` | `undefined` | -| `disabled` | `disabled` | The number of stars in the rating | `boolean` | `false` | -| `max` | `max` | The number of stars in the rating | `number` | `10` | -| `readonly` | `readonly` | The number of stars in the rating | `boolean` | `false` | +| Property | Attribute | Description | Type | Default | +| --------------- | ---------------- | --------------------------------------------------------------------------------- | --------- | ----------- | +| `currentRating` | `current-rating` | The current rating value | `number` | `undefined` | +| `disabled` | `disabled` | Boolean for the disabled state of the component | `boolean` | `false` | +| `max` | `max` | The number of stars in the rating | `number` | `10` | +| `readonly` | `readonly` | If readonly is true, the component only displays a rating and is not interactive. | `boolean` | `false` | ## Events diff --git a/packages/documentation/src/stories/components/rating/post-rating.docs.mdx b/packages/documentation/src/stories/components/rating/post-rating.docs.mdx new file mode 100644 index 0000000000..b86996d72f --- /dev/null +++ b/packages/documentation/src/stories/components/rating/post-rating.docs.mdx @@ -0,0 +1,11 @@ +import { Canvas, Controls, Meta, Source } from '@storybook/blocks'; +import LinkTo from '@storybook/addon-links/react'; +import * as PostRatingStories from './post-rating.stories'; + + + +# Rating + + + + \ No newline at end of file diff --git a/packages/documentation/src/stories/components/rating/post-rating.stories.ts b/packages/documentation/src/stories/components/rating/post-rating.stories.ts new file mode 100644 index 0000000000..65ea462cef --- /dev/null +++ b/packages/documentation/src/stories/components/rating/post-rating.stories.ts @@ -0,0 +1,26 @@ +import { Meta, StoryObj } from '@storybook/web-components'; +import { html } from 'lit'; +import { BADGE } from '../../../../.storybook/constants'; + +const meta: Meta = { + title: 'Components/Post Rating', + component: 'post-rating', + render: render, + parameters: { + badges: [BADGE.NEEDS_REVISION], + }, +}; + +export default meta; + +// RENDERER +function render() { + return html` + + `; +} + +// STORIES +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/packages/documentation/src/stories/components/rating/rating.docs.mdx b/packages/documentation/src/stories/components/rating/rating.docs.mdx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/documentation/src/stories/components/rating/rating.snapshot.stories.ts b/packages/documentation/src/stories/components/rating/rating.snapshot.stories.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/documentation/src/stories/components/rating/rating.stories.ts b/packages/documentation/src/stories/components/rating/rating.stories.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/internet-header/package.json b/packages/internet-header/package.json index 49730ff872..bc255d4c68 100644 --- a/packages/internet-header/package.json +++ b/packages/internet-header/package.json @@ -31,7 +31,7 @@ "start": "stencil build --watch --docs-readme", "build": "stencil build --docs-readme", "clean": "rimraf www dist loader", - "test": "npm run unit", + "test": "pnpm run unit", "unit": "jest", "unit:watch": "jest --watch", "e2e": "cypress run",