[READ-ONLY] Mirror of https://github.com/graphieros/vue-data-ui. An open source user-empowering data visualization Vue 3 components library for eloquent data storytelling vue-data-ui.graphieros.com/
charts components-library dashboard data-storytelling data-visualization donut gauge heatmap quadrant radar rating-stars scatter screenshot skeleton-loader sparkline table vue-data-ui vue3 vuejs vuejs3
3

Configure Feed

Select the types of activity you want to include in your feed.

VueUiRating add e2e component test

graphieros (Nov 20, 2023, 10:26 AM +0100) 25c4657d ad29f0fb

+278 -19
+71
cypress/fixtures/rating.json
··· 1 + { 2 + "dataset": { 3 + "rating": { 4 + "1": 1, 5 + "2": 1, 6 + "3": 1, 7 + "4": 1, 8 + "5": 30 9 + } 10 + }, 11 + "config": { 12 + "type": "star", 13 + "readonly": false, 14 + "from": 1, 15 + "to": 5, 16 + "style": { 17 + "fontFamily": "inherit", 18 + "animated": true, 19 + "itemSize": 32, 20 + "backgroundColor": "#FFFFFF", 21 + "star": { 22 + "activeColor": "#FFD055", 23 + "borderColor": "#FFD055", 24 + "borderWidth": 3, 25 + "apexes": 5, 26 + "inactiveColor": "#e1e5e8", 27 + "useGradient": true 28 + }, 29 + "image": { 30 + "src": "../../star.png", 31 + "inactiveOpacity": 0.3, 32 + "alt": "rating image" 33 + }, 34 + "title": { 35 + "textAlign": "center", 36 + "fontSize": 20, 37 + "color": "#2D353C", 38 + "bold": true, 39 + "text": "Title", 40 + "offsetY": 6, 41 + "subtitle": { 42 + "fontSize": 14, 43 + "color": "#CCCCCC", 44 + "bold": false, 45 + "text": "Subtitle", 46 + "offsetY": 12 47 + } 48 + }, 49 + "rating": { 50 + "show": true, 51 + "fontSize": 28, 52 + "bold": true, 53 + "roundingValue": 1, 54 + "position": "bottom", 55 + "offsetY": 0, 56 + "offsetX": 0 57 + }, 58 + "tooltip": { 59 + "show": true, 60 + "fontSize": 14, 61 + "offsetY": 0, 62 + "color": "#2D353C", 63 + "bold": true, 64 + "backgroundColor": "#FFFFFF", 65 + "borderColor": "#e1e5e8", 66 + "borderRadius": 4, 67 + "boxShadow": "0 6px 12px -6px rgba(0,0,0,0.2)" 68 + } 69 + } 70 + } 71 + }
+192
src/components/vue-ui-rating.cy.js
··· 1 + import VueUiRating from './vue-ui-rating.vue' 2 + 3 + describe('<VueUiDonut />', () => { 4 + beforeEach(function () { 5 + cy.fixture('rating.json').as('fixture'); 6 + cy.viewport(800, 160); 7 + }); 8 + 9 + function updateConfigInFixture(modifiedConfig) { 10 + cy.get('@fixture').then((fixture) => { 11 + const updatedFixture = { ...fixture, config: modifiedConfig }; 12 + cy.wrap(updatedFixture).as('fixture'); 13 + }); 14 + } 15 + 16 + it('renders with different config attributes', function () { 17 + cy.get('@fixture').then((fixture) => { 18 + cy.mount(VueUiRating, { 19 + props: { 20 + dataset: fixture.dataset, 21 + config: fixture.config 22 + } 23 + }); 24 + 25 + function calculateAverageRating(source) { 26 + let totalSum = 0; 27 + let totalCount = 0; 28 + 29 + for (const key in source) { 30 + const ratingValue = parseInt(key); 31 + const ratingCount = source[key]; 32 + 33 + totalSum += ratingValue * ratingCount; 34 + totalCount += ratingCount; 35 + } 36 + 37 + if (totalCount === 0) { 38 + return 0; 39 + } 40 + 41 + const averageRating = totalSum / totalCount; 42 + return Math.min(fixture.config.to, Math.max(fixture.config.from, averageRating)); 43 + } 44 + 45 + const staticRating = calculateAverageRating(fixture.dataset.rating).toFixed(fixture.config.style.rating.roundingValue) 46 + 47 + cy.get(`[data-cy="rating-title"]`) 48 + .should('exist') 49 + .contains('Title'); 50 + 51 + cy.get(`[data-cy="rating-subtitle"]`) 52 + .should('exist') 53 + .contains('Subtitle'); 54 + 55 + cy.get(`[data-cy="rating-position-bottom"]`) 56 + .should('exist') 57 + .contains(staticRating) 58 + 59 + let modifiedConfig = { 60 + ...fixture.config, 61 + style: { 62 + ...fixture.config.style, 63 + rating: { 64 + ...fixture.config.style.rating, 65 + position: 'top' 66 + } 67 + } 68 + } 69 + 70 + updateConfigInFixture(modifiedConfig); 71 + 72 + cy.mount(VueUiRating, { 73 + props: { 74 + dataset: fixture.dataset, 75 + config: modifiedConfig 76 + } 77 + }); 78 + 79 + cy.get(`[data-cy="rating-position-top"]`) 80 + .should('exist') 81 + .contains(staticRating); 82 + 83 + cy.wait(100).then(() => { 84 + modifiedConfig.style.rating.position = "left"; 85 + 86 + updateConfigInFixture(modifiedConfig); 87 + 88 + cy.mount(VueUiRating, { 89 + props: { 90 + dataset: fixture.dataset, 91 + config: modifiedConfig 92 + } 93 + }); 94 + 95 + cy.get(`[data-cy="rating-position-left"]`) 96 + .should('exist') 97 + .contains(staticRating); 98 + }); 99 + 100 + cy.wait(100).then(() => { 101 + 102 + modifiedConfig.style.rating.position = "right"; 103 + 104 + 105 + updateConfigInFixture(modifiedConfig); 106 + 107 + cy.mount(VueUiRating, { 108 + props: { 109 + dataset: fixture.dataset, 110 + config: modifiedConfig 111 + } 112 + }); 113 + 114 + cy.get(`[data-cy="rating-position-right"]`) 115 + .should('exist') 116 + .contains(staticRating); 117 + }); 118 + 119 + cy.wait(100).then(() => { 120 + modifiedConfig.style.rating.position = "bottom"; 121 + updateConfigInFixture(modifiedConfig); 122 + 123 + cy.mount(VueUiRating, { 124 + props: { 125 + dataset: fixture.dataset, 126 + config: modifiedConfig 127 + } 128 + }); 129 + }); 130 + 131 + const items = fixture.config.to - fixture.config.from + 1; 132 + 133 + for (let i = 0; i < items; i += 1) { 134 + cy.get(`[data-cy="rating-shape-${i}"]`) 135 + .should('exist') 136 + 137 + cy.get(`[data-cy="rating-active-trap-${i}"]`) 138 + .should('exist') 139 + .click(); 140 + 141 + cy.get(`[data-cy="rating-position-bottom"]`) 142 + .contains(`${i + 1}.0`) 143 + } 144 + 145 + cy.wait(100).then(() => { 146 + modifiedConfig.type = "image"; 147 + updateConfigInFixture(modifiedConfig); 148 + 149 + cy.mount(VueUiRating, { 150 + props: { 151 + dataset: fixture.dataset, 152 + config: modifiedConfig 153 + } 154 + }); 155 + 156 + for (let i = 0; i < items; i += 1) { 157 + cy.get(`[data-cy="rating-image-${i}"]`).then(($img) => { 158 + cy.wrap($img) 159 + .should('exist') 160 + 161 + cy.wrap($img) 162 + .invoke('attr', 'src') 163 + .should('eq', fixture.config.style.image.src) 164 + 165 + }) 166 + } 167 + 168 + }); 169 + 170 + cy.wait(100).then(() => { 171 + modifiedConfig.readonly = true; 172 + updateConfigInFixture(modifiedConfig); 173 + 174 + cy.mount(VueUiRating, { 175 + props: { 176 + dataset: fixture.dataset, 177 + config: modifiedConfig 178 + } 179 + }); 180 + 181 + for (let i = 0; i < items; i += 1) { 182 + cy.get(`[data-cy="rating-readonly-trap-${i}"]`) 183 + .trigger('mouseenter') 184 + 185 + cy.get(`[data-cy="rating-tooltip-${i}"]`) 186 + .should('exist') 187 + .contains(`${Object.keys(fixture.dataset.rating)[i]} : ${fixture.dataset.rating[`${i + 1}`]}`) 188 + } 189 + }); 190 + }); 191 + }); 192 + })
+15 -19
src/components/vue-ui-rating.vue
··· 21 21 const uid = ref(`vue-ui-rating-${Math.random()}`); 22 22 23 23 const defaultConfig = ref(mainConfig.vue_ui_rating); 24 - 25 - const isPrinting = ref(false); 26 - const ratingChart = ref(null); 27 - const tooltip = ref(null); 28 - const details = ref(null); 29 - const clientPosition = ref({ 30 - x: 0, 31 - y: 0 32 - }); 33 24 const isTooltip = ref(false); 34 - const tooltipContent = ref(""); 35 25 36 26 const hoveredValue = ref(undefined); 37 27 const units = ref([]); ··· 138 128 <div :style="`background:${ratingConfig.style.backgroundColor};font-family:${ratingConfig.style.fontFamily};width:100%`" class="vue-ui-rating" @mouseover="isTooltip = true" @mouseleave="isTooltip = false; hoveredValue = undefined"> 139 129 <!-- TITLE --> 140 130 <div class="vue-ui-rating-title" v-if="ratingConfig.style.title.text" style="width:100%"> 141 - <div :style="`color:${ratingConfig.style.title.color};font-weight:${ratingConfig.style.title.bold ? 'bold' : 'normal'};text-align:${ratingConfig.style.title.textAlign};margin-bottom:${ratingConfig.style.title.offsetY}px;font-size:${ratingConfig.style.title.fontSize}px`"> 131 + <div data-cy="rating-title" :style="`color:${ratingConfig.style.title.color};font-weight:${ratingConfig.style.title.bold ? 'bold' : 'normal'};text-align:${ratingConfig.style.title.textAlign};margin-bottom:${ratingConfig.style.title.offsetY}px;font-size:${ratingConfig.style.title.fontSize}px`"> 142 132 {{ ratingConfig.style.title.text }} 143 133 </div> 144 - <div v-if="ratingConfig.style.title.subtitle.text" :style="`color:${ratingConfig.style.title.subtitle.color};font-size:${ratingConfig.style.title.subtitle.fontSize}px;text-align:${ratingConfig.style.title.textAlign};margin-bottom:${ratingConfig.style.title.subtitle.offsetY}px;font-weight:${ratingConfig.style.title.subtitle.bold ? 'bold' : 'normal'}`"> 134 + <div data-cy="rating-subtitle" v-if="ratingConfig.style.title.subtitle.text" :style="`color:${ratingConfig.style.title.subtitle.color};font-size:${ratingConfig.style.title.subtitle.fontSize}px;text-align:${ratingConfig.style.title.textAlign};margin-bottom:${ratingConfig.style.title.subtitle.offsetY}px;font-weight:${ratingConfig.style.title.subtitle.bold ? 'bold' : 'normal'}`"> 145 135 {{ ratingConfig.style.title.subtitle.text }} 146 136 </div> 147 137 </div> 148 138 149 139 <!-- RATING POSITION TOP --> 150 - <div v-if="ratingConfig.style.rating.show && ratingConfig.style.rating.position === 'top'" :style="`width:100%;text-align:center;margin-bottom:${ratingConfig.style.rating.offsetY}px;font-size:${ratingConfig.style.rating.fontSize}px;font-weight:${ratingConfig.style.rating.bold ? 'bold' : 'normal'};margin-left:${ratingConfig.style.rating.offsetX}px`"> 140 + <div data-cy="rating-position-top" v-if="ratingConfig.style.rating.show && ratingConfig.style.rating.position === 'top'" :style="`width:100%;text-align:center;margin-bottom:${ratingConfig.style.rating.offsetY}px;font-size:${ratingConfig.style.rating.fontSize}px;font-weight:${ratingConfig.style.rating.bold ? 'bold' : 'normal'};margin-left:${ratingConfig.style.rating.offsetX}px`"> 151 141 {{ isNaN(currentRating) ? '' : currentRating.toFixed(ratingConfig.style.rating.roundingValue) }} 152 142 </div> 153 143 ··· 158 148 > 159 149 160 150 <!-- RATING POSITION LEFT --> 161 - <div v-if="ratingConfig.style.rating.show && ratingConfig.style.rating.position === 'left'" :style="`width:fit-content;text-align:center;margin-bottom:${ratingConfig.style.rating.offsetY}px;font-size:${ratingConfig.style.rating.fontSize}px;font-weight:${ratingConfig.style.rating.bold ? 'bold' : 'normal'};padding-right:${ratingConfig.style.rating.offsetX}px`"> 151 + <div data-cy="rating-position-left" v-if="ratingConfig.style.rating.show && ratingConfig.style.rating.position === 'left'" :style="`width:fit-content;text-align:center;margin-bottom:${ratingConfig.style.rating.offsetY}px;font-size:${ratingConfig.style.rating.fontSize}px;font-weight:${ratingConfig.style.rating.bold ? 'bold' : 'normal'};padding-right:${ratingConfig.style.rating.offsetX}px`"> 162 152 {{ isNaN(currentRating) ? '' : currentRating.toFixed(ratingConfig.style.rating.roundingValue) }} 163 153 </div> 164 154 ··· 169 159 :style="`position:relative;height:${ratingConfig.style.itemSize}px;width:${ratingConfig.style.itemSize}px`" 170 160 > 171 161 <!-- IMAGE FIRST LAYER --> 172 - <img 162 + <img 163 + :data-cy="`rating-image-${i}`" 173 164 v-if="isImage" 174 165 :src="ratingConfig.style.image.src" 175 166 :height="ratingConfig.style.itemSize" ··· 196 187 </radialGradient> 197 188 </defs> 198 189 <polygon 190 + :data-cy="`rating-shape-${i}`" 199 191 :points="createStar({ 200 192 plot: { x: 50, y: 50 }, 201 193 radius: 30, ··· 220 212 </svg> 221 213 222 214 <!-- IMAGE SECOND LAYER --> 223 - <img 215 + <img 216 + :data-cy="`rating-image-overlay-${i}`" 224 217 v-if="isImage" 225 218 :src="ratingConfig.style.image.src" 226 219 :alt="`${ratingConfig.style.image.alt} ${value}`" ··· 233 226 234 227 <!-- STAR SECOND LAYER --> 235 228 <svg 229 + :data-cy="`rating-shape-overlay-${i}`" 236 230 v-else 237 231 :viewBox="`0 0 ${calcShapeFill(i)} 100`" 238 232 :height="ratingConfig.style.itemSize" ··· 269 263 :style="`position:absolute;top:0;left:0;${isReadonly ? '' : 'cursor:pointer'}`" 270 264 > 271 265 <rect 266 + :data-cy="`rating-active-trap-${i}`" 272 267 class="vue-ui-rating-mouse-trap" 273 268 v-if="!isReadonly" 274 269 :x="0" ··· 283 278 @keyup.enter="rate(value)" 284 279 /> 285 280 <rect 281 + :data-cy="`rating-readonly-trap-${i}`" 286 282 class="vue-ui-rating-mouse-trap" 287 283 v-if="isReadonly" 288 284 :x="0" ··· 296 292 </svg> 297 293 <template v-if="ratingConfig.style.tooltip.show && hasBreakdown && isReadonly"> 298 294 <div class="vue-ui-rating-tooltip" :style="`border:1px solid ${ratingConfig.style.tooltip.borderColor};position:absolute;top:${-48 + ratingConfig.style.tooltip.offsetY}px;left:50%;transform:translateX(-50%);width:fit-content;text-align:center;background:${ratingConfig.style.tooltip.backgroundColor};display:${hoveredValue === value ? 'block' : 'none'};padding:2px 12px;border-radius:${ratingConfig.style.tooltip.borderRadius}px;box-shadow:${ratingConfig.style.tooltip.boxShadow}`"> 299 - <div :style="`width:100%;display:flex;flex-direction:row;gap:6px;position:relative;text-align:center;color:${ratingConfig.style.tooltip.color}`"> 295 + <div :data-cy="`rating-tooltip-${i}`" :style="`width:100%;display:flex;flex-direction:row;gap:6px;position:relative;text-align:center;color:${ratingConfig.style.tooltip.color}`"> 300 296 <span :style="`font-size:${ratingConfig.style.tooltip.fontSize}px`">{{ value }}</span> : <span :style="`font-weight:${ratingConfig.style.tooltip.bold ? 'bold' : 'normal'};font-size:${ratingConfig.style.tooltip.fontSize}px`">{{ props.dataset.rating[value] }}</span> 301 297 <div :style="`font-family:Arial !important;position:absolute;top:calc(100% - 4px);left:50%;transform:translateX(-50%);color:${ratingConfig.style.tooltip.borderColor}`"> 302 298 ··· 309 305 310 306 311 307 <!-- RATING POSITION RIGHT --> 312 - <div v-if="ratingConfig.style.rating.show && ratingConfig.style.rating.position === 'right'" :style="`width:fit-content;text-align:center;margin-bottom:${ratingConfig.style.rating.offsetY}px;font-size:${ratingConfig.style.rating.fontSize}px;font-weight:${ratingConfig.style.rating.bold ? 'bold' : 'normal'};padding-left:${ratingConfig.style.rating.offsetX}px`"> 308 + <div data-cy="rating-position-right" v-if="ratingConfig.style.rating.show && ratingConfig.style.rating.position === 'right'" :style="`width:fit-content;text-align:center;margin-bottom:${ratingConfig.style.rating.offsetY}px;font-size:${ratingConfig.style.rating.fontSize}px;font-weight:${ratingConfig.style.rating.bold ? 'bold' : 'normal'};padding-left:${ratingConfig.style.rating.offsetX}px`"> 313 309 {{ isNaN(currentRating) ? '' : currentRating.toFixed(ratingConfig.style.rating.roundingValue) }} 314 310 </div> 315 311 316 312 </div> 317 313 318 314 <!-- RATING POSITION BOTTOM --> 319 - <div v-if="ratingConfig.style.rating.show && ratingConfig.style.rating.position === 'bottom'" :style="`width:100%;text-align:center;margin-top:${ratingConfig.style.rating.offsetY}px;font-size:${ratingConfig.style.rating.fontSize}px;font-weight:${ratingConfig.style.rating.bold ? 'bold' : 'normal'};margin-left:${ratingConfig.style.rating.offsetX}px`"> 315 + <div data-cy="rating-position-bottom" v-if="ratingConfig.style.rating.show && ratingConfig.style.rating.position === 'bottom'" :style="`width:100%;text-align:center;margin-top:${ratingConfig.style.rating.offsetY}px;font-size:${ratingConfig.style.rating.fontSize}px;font-weight:${ratingConfig.style.rating.bold ? 'bold' : 'normal'};margin-left:${ratingConfig.style.rating.offsetX}px`"> 320 316 {{ isNaN(currentRating) ? '' : currentRating.toFixed(ratingConfig.style.rating.roundingValue) }} 321 317 </div> 322 318
star.png

This is a binary file and will not be displayed.