diff --git a/src/components/base/progress_bar/progress_bar.stories.js b/src/components/base/progress_bar/progress_bar.stories.js index 0f03c66d5206d45d12c60a91e57929bc703e6c5f..d8c159e9532f3c6e8714f57ab084fcf0133364b1 100644 --- a/src/components/base/progress_bar/progress_bar.stories.js +++ b/src/components/base/progress_bar/progress_bar.stories.js @@ -1,5 +1,6 @@ import { progressBarVariantOptions } from '../../../utils/constants'; import GlProgressBar from './progress_bar.vue'; +import GlStackedProgressBar from './stacked_progress_bar.vue'; const generateProps = ({ value = 30, @@ -37,6 +38,27 @@ export const Variants = (args, { argTypes = {} }) => ({ Variants.args = generateProps(); Variants.parameters = { controls: { disable: true } }; +export const Stacked = (args, { argTypes = {} }) => ({ + components: { GlStackedProgressBar }, + props: Object.keys(argTypes), + template: ` + + `, +}); +Stacked.args = { + segments: [ + { id: 'critical-segment', value: 800, classes: ['gl-bg-red-800'] }, + { id: 'high-segment', value: 200, classes: ['gl-bg-red-600'] }, + { id: 'medium-segment', value: 108, classes: ['gl-bg-orange-400'] }, + { id: 'low-segment', value: 100, classes: ['gl-bg-orange-300'] }, + ], + max: 1208, +}; +Stacked.parameters = { controls: { disable: true } }; + export default { title: 'base/progress-bar', component: GlProgressBar, diff --git a/src/components/base/progress_bar/stacked_progress_bar.spec.js b/src/components/base/progress_bar/stacked_progress_bar.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..bc54741b1558b5213724c6fa77dceb86cc5eb9a0 --- /dev/null +++ b/src/components/base/progress_bar/stacked_progress_bar.spec.js @@ -0,0 +1,104 @@ +import { mount } from '@vue/test-utils'; +import StackedProgressBar from './stacked_progress_bar.vue'; + +describe('GlStackedProgressBar', () => { + let wrapper; + + const segments = [ + { id: 1, value: 25 }, + { id: 2, value: 10 }, + ]; + + const createWrapper = ( + propsData = { + segments, + } + ) => { + wrapper = mount(StackedProgressBar, { + propsData, + }); + }; + + const findProgressAt = (i) => wrapper.findAll('[role="progressbar"]').at(i); + + beforeEach(() => { + createWrapper(); + }); + + describe('default', () => { + it('renders wrapper with expected classes and style', () => { + expect(wrapper.classes()).toMatchObject(['gl-stacked-progress-bar', 'progress']); + expect(wrapper.attributes('style')).toBeUndefined(); + }); + + it.each` + index | value + ${0} | ${25} + ${1} | ${10} + `('renders children with expected classes and attributes', ({ index, value }) => { + const progress = findProgressAt(index); + + expect(progress.classes()).toMatchObject(['gl-progress']); + expect(progress.attributes('style')).toBe(`width: ${value}%;`); + expect(progress.attributes('aria-label')).toBe('Stacked progress bar'); + expect(progress.attributes('aria-valuemin')).toBe('0'); + expect(progress.attributes('aria-valuemax')).toBe('100'); + expect(progress.attributes('aria-valuenow')).toBe(`${value}`); + }); + }); + + describe('value', () => { + it('sets style and attributes correctly when setting `value`', () => { + const value = 65.6; + createWrapper({ segments: [{ id: 1, value }] }); + + const progress = findProgressAt(0); + const computedValue = parseFloat(value); + + expect(progress.attributes('style')).toBe(`width: ${computedValue}%;`); + expect(progress.attributes('aria-valuenow')).toBe(`${parseFloat(value)}`); + }); + }); + + describe('ariaLabel', () => { + it('sets value from prop', () => { + const label = 'Progress'; + createWrapper({ segments, ariaLabel: label }); + + const progress = findProgressAt(0); + + expect(progress.attributes('aria-label')).toBe('Progress'); + }); + }); + + describe('max', () => { + it('sets style and attributes correctly when using custom `max`', () => { + const value = 45.1; + const max = 75.5; + createWrapper({ segments: [{ id: 1, value }], max }); + + const progress = findProgressAt(0); + const computedValue = (parseFloat(value) / parseFloat(max)) * 100; + + expect(progress.attributes('style')).toBe(`width: ${computedValue}%;`); + expect(progress.attributes('aria-valuemax')).toBe(`${parseFloat(max)}`); + expect(progress.attributes('aria-valuenow')).toBe(`${parseFloat(value)}`); + }); + }); + + describe('segment classes', () => { + it.each(['primary', 'gl-bg-red-800'])('sets correct css class for variant %s', (cssClass) => { + createWrapper({ segments: [{ id: 1, value: 50, classes: [cssClass] }] }); + + expect(findProgressAt(0).classes()).toMatchObject(['gl-progress', cssClass]); + }); + }); + + describe('height', () => { + it('sets height correctly', () => { + createWrapper({ segments, height: '5px' }); + + expect(wrapper.attributes('style')).toBe('height: 5px;'); + }); + }); +}); diff --git a/src/components/base/progress_bar/stacked_progress_bar.vue b/src/components/base/progress_bar/stacked_progress_bar.vue new file mode 100644 index 0000000000000000000000000000000000000000..c52eb90e01410ec8b710e46d8fca7b8c6948ca0e --- /dev/null +++ b/src/components/base/progress_bar/stacked_progress_bar.vue @@ -0,0 +1,66 @@ + + + diff --git a/tests/__image_snapshots__/storyshots-spec-js-image-storyshots-base-progress-bar-stacked-1-snap.png b/tests/__image_snapshots__/storyshots-spec-js-image-storyshots-base-progress-bar-stacked-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..819c24784de66c43587e07adf2cc00fd80634d25 Binary files /dev/null and b/tests/__image_snapshots__/storyshots-spec-js-image-storyshots-base-progress-bar-stacked-1-snap.png differ diff --git a/translations.js b/translations.js index 31028804052e55a82916c8376b183a194b17cee2..5e9b49ed3eb5b03eddcd4d04fcba3409ba62d872 100644 --- a/translations.js +++ b/translations.js @@ -26,5 +26,6 @@ export default { 'GlSearchBoxByType.input.placeholder': 'Search', 'GlSorting.sortAscending': 'Sort direction: ascending', 'GlSorting.sortDescending': 'Sort direction: descending', + 'GlStackedProgressBar.ariaLabel': 'Stacked progress bar', 'GlToken.closeButtonTitle': 'Remove', };