From 3219260e1d8b8fa6860256f2fc13ef799894e827 Mon Sep 17 00:00:00 2001 From: Kevin Rodrigues Date: Thu, 20 Oct 2022 13:31:02 +0100 Subject: [PATCH] f-rating@0.4.0 - Refactor component to allow fractional stars. (#2170) * f-rating@0.4.0 - Refactor component to allow fractional stars. * f-rating@0.4.0 - Fix test comment. * f-rating@0.4.0 - Remove template string. * f-rating@0.4.0 - Update test description. * f-rating@0.4.0 - PR comments. --- .../molecules/f-rating/CHANGELOG.md | 13 ++ .../components/molecules/f-rating/README.md | 1 + .../molecules/f-rating/package.json | 2 +- .../f-rating/src/components/Rating.vue | 103 +++++++++++----- .../src/components/_tests/Rating.test.js | 115 ++++++++++-------- .../_tests/__snapshots__/Rating.test.js.snap | 6 - .../molecules/f-rating/src/constants.js | 6 + .../f-rating/src/tests/constants.test.js | 17 +++ .../f-rating/stories/Rating.stories.js | 3 +- 9 files changed, 177 insertions(+), 89 deletions(-) create mode 100644 packages/components/molecules/f-rating/src/constants.js create mode 100644 packages/components/molecules/f-rating/src/tests/constants.test.js diff --git a/packages/components/molecules/f-rating/CHANGELOG.md b/packages/components/molecules/f-rating/CHANGELOG.md index 5d1baf6355..f815c412a9 100644 --- a/packages/components/molecules/f-rating/CHANGELOG.md +++ b/packages/components/molecules/f-rating/CHANGELOG.md @@ -3,6 +3,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +v0.4.0 +------------------------------ +*October 19, 2022* + +### Added +- Test coverage for code changes. +- `starRatingSize` to allow component sizing. +- Prop validation. + +### Changed +- Refactored markup to allow fractional rating sizes. + + v0.3.0 ------------------------------ *October 17, 2022* diff --git a/packages/components/molecules/f-rating/README.md b/packages/components/molecules/f-rating/README.md index 46085d5a80..25d08f4f94 100644 --- a/packages/components/molecules/f-rating/README.md +++ b/packages/components/molecules/f-rating/README.md @@ -73,6 +73,7 @@ The props that can be defined are as follows (if any): | :--- |:--------:|:--------:|:-------:|:-----------------------------------------------------------------------------------------------------------------------| | `starRating` | `Number` | Yes | - | Sets the displayed rating (filled stars). i.e. for `2 out of x`, 2 would be the `starRating` | | `maxStarRating` | `Number` | No | 5 | Sets the maximum number of stars that the rating is set against. i.e. for `x out of 5`, 5 would be the `maxStarRating` | +| `starRatingSize` | `String` | No | 'small' | Sets the component size. By default the component will use `small` the other options are `medium` & `large` | ### Events diff --git a/packages/components/molecules/f-rating/package.json b/packages/components/molecules/f-rating/package.json index ec7afcd7e9..60495f6f22 100644 --- a/packages/components/molecules/f-rating/package.json +++ b/packages/components/molecules/f-rating/package.json @@ -1,7 +1,7 @@ { "name": "@justeat/f-rating", "description": "Fozzie Rating - Global Rating component", - "version": "0.3.0", + "version": "0.4.0", "main": "dist/f-rating.umd.min.js", "maxBundleSize": "20kB", "files": [ diff --git a/packages/components/molecules/f-rating/src/components/Rating.vue b/packages/components/molecules/f-rating/src/components/Rating.vue index 524f1d40ac..15f1827464 100644 --- a/packages/components/molecules/f-rating/src/components/Rating.vue +++ b/packages/components/molecules/f-rating/src/components/Rating.vue @@ -2,25 +2,39 @@
- - - {{ getRatingDescription }} - + v-for="star in maxStarRating" + :key="star" + :class="[ + $style['c-rating-star-empty'], + $style[`c-rating-star--${starRatingSize}`] + ]" /> +
+ +
+ +
+ + + {{ getRatingDescription }} + + @@ -31,6 +45,7 @@ import { } from '@justeattakeaway/pie-icons-vue'; import { VueGlobalisationMixin } from '@justeat/f-globalisation'; import tenantConfigs from '../tenants'; +import { VALID_STAR_RATING_SIZES } from '../constants'; export default { name: 'VRating', @@ -48,11 +63,17 @@ export default { }, starRating: { type: Number, - required: true + required: true, + validator: value => value >= 0 && value <= 5 }, maxStarRating: { type: Number, default: 5 + }, + starRatingSize: { + type: String, + default: 'small', + validator: value => !!VALID_STAR_RATING_SIZES[value] } }, @@ -80,18 +101,15 @@ export default { rating: this.starRating, total: this.maxStarRating }); - } - }, + }, - methods: { /** - * Check `star` against value passed by consumer to allow empty stars to render. + * Calculate a percentage from the `starRating` value passed in by the consuming application. * - * @param star {Number} - * @returns {boolean} + * @returns {string} */ - isRatingStarFilled (star) { - return star <= this.starRating; + getRatingStarPercentage () { + return `${(this.starRating / this.maxStarRating) * 100}%`; } } }; @@ -103,12 +121,25 @@ export default { .c-rating-starWrapper { margin: 0; padding: 0; - list-style-type: none; + position: relative; + display: inline-block; } + .c-rating-container { + display: flex; + } .c-rating-star { - display: inline-block; - width: 15px; // Todo - decide on how to size these. Will create a ticket around this. + &--small { + width: 12px; + } + + &--medium { + width: 16px; + } + + &--large { + width: 28px; + } } .c-rating-star-filled { @@ -122,4 +153,16 @@ export default { fill: f.$color-mozzarella-50; } } + + .c-rating-mask { + position: absolute; + top: 0; + left: 0; + overflow: hidden; + width: var(--starRatingPercentage); + + svg { + flex-shrink: 0; + } + } diff --git a/packages/components/molecules/f-rating/src/components/_tests/Rating.test.js b/packages/components/molecules/f-rating/src/components/_tests/Rating.test.js index 70cfac5168..bb697b61ba 100644 --- a/packages/components/molecules/f-rating/src/components/_tests/Rating.test.js +++ b/packages/components/molecules/f-rating/src/components/_tests/Rating.test.js @@ -30,74 +30,39 @@ describe('Rating', () => { expect(wrapper.exists()).toBe(true); }); - describe('methods', () => { - describe('`isRatingStarFilled`', () => { + describe('computed', () => { + describe('`getRatingStarPercentage`', () => { it('should exist', () => { - expect(wrapper.vm.isRatingStarFilled).toBeDefined(); - }); - - it('should contain a description `c-rating-description`', () => { - // Act - const result = wrapper.find('[data-test-id="c-rating-description"]'); + // Arrange + propsData.starRating = 2; + wrapper = shallowMount(VRating, { + propsData, + localVue, + i18n + }); - // Assert - expect(result).toMatchSnapshot(); + // Act & Assert + expect(wrapper.vm.getRatingStarPercentage).toBeDefined(); }); describe('when invoked', () => { - it('should return truthy when the argument `star` is less than or equal to `starRating`', () => { - // Act - const star = 1; - const result = wrapper.vm.isRatingStarFilled(star); - - // Assert - expect(result).toBe(true); - }); - - it.each([1, 2])('should return truthy when the argument star is %s', value => { + it('should return a percentage from a combination of `starRating` and `maxStarRating`', () => { // Arrange propsData = { - starRating: value, - maxStarRating: 5 + starRating: 2 }; - - // Act wrapper = shallowMount(VRating, { propsData, localVue, - i18n, - mocks: { - $tc - } + i18n }); - const result = wrapper.vm.isRatingStarFilled(value); - - // Assert - expect(result).toBe(true); - }); - - it('should return truthy when the argument `star` is equal to `starRating`', () => { - // Act - const star = 2; - const result = wrapper.vm.isRatingStarFilled(star); - // Assert - expect(result).toBe(true); - }); - - it('should return falsey when argument `star` is greater than `starRating`', () => { - // Act - const star = 3; - const result = wrapper.vm.isRatingStarFilled(star); - - // Assert - expect(result).toBe(false); + // Act & Assert + expect(wrapper.vm.getRatingStarPercentage).toBe('40%'); }); }); }); - }); - describe('computed', () => { describe('`getRatingDescription`', () => { it('should exist', () => { // Arrange @@ -145,4 +110,52 @@ describe('Rating', () => { }); }); }); + + describe('props', () => { + describe('`starRatingSize`', () => { + it('should set a default value of `small`', () => { + // Act & Assert + expect(VRating.props.starRatingSize.default).toBe('small'); + }); + + it('should return a true when type prop of exists', () => { + // Act + const { validator } = VRating.props.starRatingSize; + + // Arrange + expect(validator('small')).toBe(true); + }); + + it('should NOT allow invalid props', () => { + // Arrange + const { validator } = VRating.props.starRatingSize; + + // Act & Assert + expect(validator('supernova')).toBe(false); + }); + }); + + describe('`starRating`', () => { + it('should be required', () => { + // Act & Assert + expect(VRating.props.starRating.required).toBe(true); + }); + + it.each([0, 1, 2, 3, 4, 5])('should allow value `%s`', rating => { + // Act + const { validator } = VRating.props.starRating; + + // Assert + expect(validator(rating)).toBe(true); + }); + + it('should NOT only allow values outside `0 - 5`', () => { + // Arrange + const { validator } = VRating.props.starRating; + + // Act & Assert + expect(validator(6)).toBe(false); + }); + }); + }); }); diff --git a/packages/components/molecules/f-rating/src/components/_tests/__snapshots__/Rating.test.js.snap b/packages/components/molecules/f-rating/src/components/_tests/__snapshots__/Rating.test.js.snap index ef953d7f7c..312ca3e643 100644 --- a/packages/components/molecules/f-rating/src/components/_tests/__snapshots__/Rating.test.js.snap +++ b/packages/components/molecules/f-rating/src/components/_tests/__snapshots__/Rating.test.js.snap @@ -3,9 +3,3 @@ exports[`Rating computed \`getRatingDescription\` when invoked should return a plural description if the rating is greater than 1 1`] = `"2 stars out of 5"`; exports[`Rating computed \`getRatingDescription\` when invoked should return a singular description if the rating is less than 2 1`] = `"1 star out of 5"`; - -exports[`Rating methods \`isRatingStarFilled\` should contain a description \`c-rating-description\` 1`] = ` - - - -`; diff --git a/packages/components/molecules/f-rating/src/constants.js b/packages/components/molecules/f-rating/src/constants.js new file mode 100644 index 0000000000..abac44c400 --- /dev/null +++ b/packages/components/molecules/f-rating/src/constants.js @@ -0,0 +1,6 @@ +// eslint-disable-next-line import/prefer-default-export +export const VALID_STAR_RATING_SIZES = { + small: true, + medium: true, + large: true +}; diff --git a/packages/components/molecules/f-rating/src/tests/constants.test.js b/packages/components/molecules/f-rating/src/tests/constants.test.js new file mode 100644 index 0000000000..242fa6b1b9 --- /dev/null +++ b/packages/components/molecules/f-rating/src/tests/constants.test.js @@ -0,0 +1,17 @@ +import { VALID_STAR_RATING_SIZES } from '../constants'; + +describe('`constants`', () => { + describe('VALID_STAR_RATING_SIZES', () => { + it('should contain the correct sizes', () => { + // Arrange + const values = { + small: true, + medium: true, + large: true + }; + + // Act & Assert + expect(VALID_STAR_RATING_SIZES).toEqual(values); + }); + }); +}); diff --git a/packages/components/molecules/f-rating/stories/Rating.stories.js b/packages/components/molecules/f-rating/stories/Rating.stories.js index c14e834a9d..e60b1d6d27 100644 --- a/packages/components/molecules/f-rating/stories/Rating.stories.js +++ b/packages/components/molecules/f-rating/stories/Rating.stories.js @@ -14,7 +14,8 @@ export const RatingComponent = (args, { argTypes }) => ({ template: `` + :starRatingSize="'medium'" + :starRating="2.5" />` }); RatingComponent.storyName = 'f-rating';