Skip to content

Commit

Permalink
chore: add test to storybook utils (#1121)
Browse files Browse the repository at this point in the history
* chore: add test to storybook utils

* chore: hit 100% test coverage

Co-authored-by: Lee Chase <lee.chase@uk.ibm.com>
  • Loading branch information
lee-chase and lee-chase authored Feb 15, 2021
1 parent ffee9e2 commit 3e71c95
Show file tree
Hide file tree
Showing 15 changed files with 323 additions and 31 deletions.
10 changes: 9 additions & 1 deletion packages/v3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,15 @@
],
"jest": {
"collectCoverage": true,
"collectCoverageFrom": ["src/*/**/*.{js,vue}", "!**/node_modules/**"],
"collectCoverageFrom": ["src/*/**/*.{js,vue}", "!**/node_modules/**", "!**/*.stories.js", "!**/*.spec.js", "!**/*.test.js"],
"coverageThreshold": {
"global": {
"branches": 100,
"functions": 100,
"lines": 100,
"statements": 0
}
},
"preset": "@vue/cli-plugin-unit-jest",
"transform": {
"^.+\\.vue$": "vue-jest"
Expand Down
4 changes: 2 additions & 2 deletions packages/v3/src/components/CvButton/CvButton.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { shallowRef } from '@vue/reactivity';

import CvButton from './CvButton';
import { buttonKinds, buttonSizes } from './consts.js';
import commonPropsControls from '../../global/storybook-utils';
import { storybookControlsFromProps } from '../../global/storybook-utils';

import { props as commonCvButtonProps } from './CvButtonCommon';
import {
Expand All @@ -27,7 +27,7 @@ export default {
title: 'Components/CvButton',
component: CvButton,
argTypes: {
...commonPropsControls(commonCvButtonProps),
...storybookControlsFromProps(commonCvButtonProps),
icon: { control: { type: 'select', options: Object.keys(icons) } },
kind: { control: { type: 'select', options: buttonKinds } },
size: { control: { type: 'select', options: buttonSizes } },
Expand Down
10 changes: 8 additions & 2 deletions packages/v3/src/components/CvButton/CvButton.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
<template>
<button :class="buttonClasses" :disabled="disabled || skeleton">
<button
:class="buttonClasses"
:disabled="disabled || skeleton"
@click="$emit('click', $event)"
>
<!-- @slot Default content of button -->
<slot v-if="!skeleton" />
<!-- <slot v-if="!skeleton" /> -->
<slot></slot>

<cv-svg
v-if="!skeleton && icon"
Expand All @@ -24,6 +29,7 @@ const { disabled, icon, kind, size } = commonCvButtonProps;
export default {
name: 'CvButton',
components: { CvSvg },
emits: ['click'], // emitted to allow testing of click
props: {
// Docgen comments added for storybook doc page
/**
Expand Down
6 changes: 0 additions & 6 deletions packages/v3/src/components/CvButton/CvButtonCommon.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@ export const props = {
icon: {
type: [String, Object],
default: undefined,
validator(val) {
if (!val || typeof val === 'string') {
return true;
}
return val.render !== null;
},
},
kind: {
type: String,
Expand Down
4 changes: 2 additions & 2 deletions packages/v3/src/components/CvButton/CvIconButton.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { shallowRef } from '@vue/reactivity';

import CvIconButton from './CvIconButton';
import { buttonKinds, buttonSizes } from './consts.js';
import commonPropsControls from '../../global/storybook-utils';
import { storybookControlsFromProps } from '../../global/storybook-utils';

import { props as commonCvButtonProps } from './CvButtonCommon';
import {
Expand All @@ -27,7 +27,7 @@ export default {
title: 'Components/CvIconButton',
component: CvIconButton,
argTypes: {
...commonPropsControls(commonCvButtonProps),
...storybookControlsFromProps(commonCvButtonProps),
/**
* \@carbon/icons-vue icon, href, svg or symbol
*/
Expand Down
2 changes: 2 additions & 0 deletions packages/v3/src/components/CvButton/CvIconButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
`${carbonPrefix}--tooltip--align-${tipAlignment || 'center'}`,
]"
:disabled="disabled"
@click="$emit('click', $event)"
>
<span :class="`${carbonPrefix}--assistive-text`">{{ label }}</span>

Expand All @@ -28,6 +29,7 @@ const { disabled, icon, kind, size } = commonCvButtonProps;
export default {
name: 'CvIconButton',
components: { CvSvg },
emits: ['click'], // emitted to allow testing of click
props: {
// Docgen comments added for storybook doc page
/**
Expand Down
12 changes: 9 additions & 3 deletions packages/v3/src/components/CvButton/__tests__/CvButton.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { carbonPrefix } from '../../../global/settings';
import { CvButton } from '..';

describe('CvButton', () => {
it('renders button and slot', () => {
it('renders button and slot', async () => {
// const onClick = jest.fn();
const slotContent = 'slot content';
const wrapper = shallowMount(CvButton, {
slots: {
Expand All @@ -16,7 +17,12 @@ describe('CvButton', () => {
expect(button.classes()).toContain(`${carbonPrefix}--btn`);
expect(button.classes()).toContain(`${carbonPrefix}--btn--primary`);

console.dir(button.innerHTML);
// expect(button.find(slotContent)).toBe(true);
expect(button.text()).toBe(slotContent);
});

it('Raises click event when clicked', async () => {
const wrapper = shallowMount(CvButton);
await wrapper.find('button').trigger('click');
expect(wrapper.emitted('click')).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { carbonPrefix } from '../../../global/settings';
import { props, useCvButtonCommon } from '../CvButtonCommon';
import { CvButtonConsts } from '..';

describe('CvButtonCommon.props', () => {
it('has no default icon', () => {
expect(props.icon.default).not.toBeDefined();
});

it('has a default kind of primary', () => {
expect(props.kind.default === 'primary').toBe(true);
});

it('validates kind', () => {
const spy = jest.spyOn(console, 'error').mockImplementation();

const validator = props.kind.validator;
CvButtonConsts.buttonKinds.forEach(kind => {
expect(validator(kind)).toBe(true);
});
expect(spy).not.toBeCalled();

expect(validator('banana is not a valid kind')).toBe(false);
expect(spy).toBeCalled();

spy.mockRestore(); // Remove mock
});

it('validates size', () => {
const spy = jest.spyOn(console, 'error').mockImplementation();

const validator = props.size.validator;
CvButtonConsts.buttonSizes.forEach(size => {
expect(validator(size)).toBe(true);
});
expect(spy).not.toBeCalled();

expect(validator('banana is not a valid size')).toBe(false);
expect(spy).toBeCalled();

spy.mockRestore(); // Remove mock
});
});

describe('CvButtonCommon.useCvButtonCommon', () => {
let { buttonClasses } = useCvButtonCommon('primary', 'sm', false, true);
expect(buttonClasses.value).toEqual([
`${carbonPrefix}--btn`,
`${carbonPrefix}--btn--icon-only`,
`${carbonPrefix}--btn--primary`,
`${carbonPrefix}--btn--sm`,
]);

buttonClasses = useCvButtonCommon('wibble', 'sm', true, true).buttonClasses;
expect(buttonClasses.value).toEqual([
`${carbonPrefix}--btn`,
`${carbonPrefix}--skeleton`,
`${carbonPrefix}--btn--icon-only`,
`${carbonPrefix}--btn--sm`,
]);

buttonClasses = useCvButtonCommon('secondary', 'small').buttonClasses;
expect(buttonClasses.value).toEqual([
`${carbonPrefix}--btn`,
`${carbonPrefix}--btn--secondary`,
`${carbonPrefix}--btn--sm`,
]);
});
28 changes: 28 additions & 0 deletions packages/v3/src/components/CvButton/__tests__/CvIconButton.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { shallowMount } from '@vue/test-utils';
import { carbonPrefix } from '../../../global/settings';

import { CvIconButton } from '..';

describe('CvIconButton', () => {
it('renders button and slot', () => {
const labelContent = 'label content';
const wrapper = shallowMount(CvIconButton, {
props: {
label: labelContent,
},
});

const button = wrapper.find('button');
expect(button.classes()).toContain(`${carbonPrefix}--btn`);
expect(button.classes()).toContain(`${carbonPrefix}--btn--primary`);

const assistiveText = button.find(`.${carbonPrefix}--assistive-text`);
expect(assistiveText.text()).toBe(labelContent);
});

it('Raises click event when clicked', async () => {
const wrapper = shallowMount(CvIconButton);
await wrapper.find('button').trigger('click');
expect(wrapper.emitted('click')).toHaveLength(1);
});
});
8 changes: 7 additions & 1 deletion packages/v3/src/components/CvSvg/_CvSvg.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ export default {
if (!val || typeof val === 'string') {
return true;
}
return val.setup !== null;
const result = val.render !== undefined;
if (!result) {
console.error(
'Expected a Vue icon component, SVG, SVG Symbol or SVG file'
);
}
return result;
},
},
},
Expand Down
57 changes: 57 additions & 0 deletions packages/v3/src/components/CvSvg/__tests__/CvSvg.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { shallowMount } from '@vue/test-utils';

import CvSvg from '../_CvSvg';
import TestIcon from './_TestIcon';

const notAComponent = {};
const fakeComponentVue = { render: 'Vue 2' };
const svgInline = /* html */ `<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" id="root"> <g fill-rule="evenodd" id="g"> <path id="path" d="M7 7H4v2h3v3h2V9h3V7H9V4H7v3zm1 9A8 8 0 1 1 8 0a8 8 0 0 1 0 16z"/> </g> </svg>`;
const svgSymbol = './test-svg.svg#test-symbol';
const svgFie = './test-svg.svg';

describe('Svg with fake icon', () => {
it('renders the SVG from an icon component', () => {
const wrapper = shallowMount(CvSvg, {
props: { svg: TestIcon },
});

expect(wrapper.find('svg')).not.toBeNull();
});

it('renders the SVG from an svg file', () => {
const wrapper = shallowMount(CvSvg, {
props: { svg: svgFie },
});

expect(wrapper.find('svg')).not.toBeNull();
});

it('renders the SVG symbol', () => {
const wrapper = shallowMount(CvSvg, {
props: { svg: svgSymbol },
});

expect(wrapper.find('svg')).not.toBeNull();
});

it('renders the SVG inline', () => {
const wrapper = shallowMount(CvSvg, {
props: { svg: svgInline },
});

expect(wrapper.find('svg')).not.toBeNull();
});

it('validates icon', () => {
const spy = jest.spyOn(console, 'error').mockImplementation();

const validator = CvSvg.props.svg.validator;
expect(validator(fakeComponentVue)).toBe(true);
expect(spy).not.toBeCalled();

expect(validator(notAComponent)).toBe(false);
expect(spy).toBeCalledTimes(1);

spy.mockRestore(); // Remove mock
});
});
19 changes: 19 additions & 0 deletions packages/v3/src/components/CvSvg/__tests__/_TestIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" id="root">
<g fill-rule="evenodd" id="g">
<path
id="path"
d="M7 7H4v2h3v3h2V9h3V7H9V4H7v3zm1 9A8 8 0 1 1 8 0a8 8 0 0 1 0 16z"
/>
</g>
</svg>
</template>

<script>
export default {
name: 'TestIcon',
// setup() {
// // empty setup
// },
};
</script>
5 changes: 5 additions & 0 deletions packages/v3/src/components/CvSvg/__tests__/test-svg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions packages/v3/src/global/__tests__/storybook-utils.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { storybookControlsFromProps } from '../storybook-utils';

const props = {
boolean_0: true,
boolean_1: Boolean,
boolean_2: { type: Boolean },
boolean_3: { type: Boolean, default: true },
boolean_4: { default: true },
string_0: 'test',
string_1: String,
string_2: { type: String },
string_3: { type: String, default: 'test' },
string_4: { default: 'test' },
number_0: 1,
number_1: Number,
number_2: { type: Number },
number_3: { type: Number, default: 1 },
number_4: { default: 1 },
errNotSure: {},
errNotSure2: { type: {} },
};

const expectedResult = {
boolean_0: { control: { type: 'boolean', default: true } },
boolean_1: { control: { type: 'boolean', default: false } },
boolean_2: { control: { type: 'boolean', default: false } },
boolean_3: { control: { type: 'boolean', default: true } },
boolean_4: { control: { type: 'boolean', default: true } },
string_0: { control: { type: 'text', default: 'test' } },
string_1: { control: { type: 'text', default: '' } },
string_2: { control: { type: 'text', default: '' } },
string_3: { control: { type: 'text', default: 'test' } },
string_4: { control: { type: 'text', default: 'test' } },
number_0: { control: { type: 'number', default: 1 } },
number_1: { control: { type: 'number', default: 0 } },
number_2: { control: { type: 'number', default: 0 } },
number_3: { control: { type: 'number', default: 1 } },
number_4: { control: { type: 'number', default: 1 } },
errNotSure: { control: { type: 'text', default: '' } },
errNotSure2: { control: { type: 'text', default: '' } },
};

describe('global/storybook-utils', () => {
it('Should convert common props to storybook control types', () => {
const controls = storybookControlsFromProps(props);

expect(controls).toEqual(expectedResult);
});
});
Loading

0 comments on commit 3e71c95

Please sign in to comment.