mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-02 05:03:44 +08:00
Merge pull request #14684 from storybookjs/test/addon-controls-infer-and-e2e
Controls: Tighten color control inference heuristic and test
This commit is contained in:
commit
2626146dbe
49
cypress/generated/addon-controls.spec.ts
Normal file
49
cypress/generated/addon-controls.spec.ts
Normal file
@ -0,0 +1,49 @@
|
||||
describe('addon-controls', () => {
|
||||
it('should change component when changing controls', () => {
|
||||
cy.visitStorybook();
|
||||
|
||||
cy.navigateToStory('example-button', 'Primary');
|
||||
|
||||
cy.viewAddonPanel('Controls');
|
||||
|
||||
// Text input: Label
|
||||
cy.getStoryElement().find('button').should('contain.text', 'Button');
|
||||
cy.get('#label').clear().type('Hello world');
|
||||
cy.getStoryElement().find('button').should('contain.text', 'Hello world');
|
||||
|
||||
// Args in URL
|
||||
cy.url().should('include', 'args=label:Hello+world');
|
||||
|
||||
// Boolean toggle: Primary/secondary
|
||||
cy.getStoryElement().find('button').should('have.css', 'background-color', 'rgb(30, 167, 253)');
|
||||
cy.get('#primary').click();
|
||||
cy.getStoryElement().find('button').should('have.css', 'background-color', 'rgba(0, 0, 0, 0)');
|
||||
|
||||
// Color picker: Background color
|
||||
cy.get('input[placeholder="Choose color"]').type('red');
|
||||
cy.getStoryElement().find('button').should('have.css', 'background-color', 'rgb(255, 0, 0)');
|
||||
|
||||
// TODO: enable this once the controls for size are aligned in all CLI templates.
|
||||
// Radio buttons: Size
|
||||
// cy.getStoryElement().find('button').should('have.css', 'font-size', '14px');
|
||||
// cy.get('label[for="size-large"]').click();
|
||||
// cy.getStoryElement().find('button').should('have.css', 'font-size', '16px');
|
||||
|
||||
// Reset controls: assert that the component is back to original state
|
||||
cy.get('button[title="Reset controls"]').click();
|
||||
cy.getStoryElement().find('button').should('have.css', 'font-size', '14px');
|
||||
cy.getStoryElement().find('button').should('have.css', 'background-color', 'rgb(30, 167, 253)');
|
||||
cy.getStoryElement().find('button').should('contain.text', 'Button');
|
||||
});
|
||||
|
||||
it('should apply controls automatically when passed via url', () => {
|
||||
cy.visit('/', {
|
||||
qs: {
|
||||
path: '/story/example-button--primary',
|
||||
args: 'label:Hello world',
|
||||
},
|
||||
});
|
||||
|
||||
cy.getStoryElement().find('button').should('contain.text', 'Hello world');
|
||||
});
|
||||
});
|
@ -1,17 +1,18 @@
|
||||
import { html } from 'lit-html';
|
||||
import { styleMap } from 'lit-html/directives/style-map';
|
||||
import './button.css';
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
*/
|
||||
export const Button = ({ primary, backgroundColor, size, label, onClick }) => {
|
||||
export const Button = ({ primary, backgroundColor = null, size, label, onClick }) => {
|
||||
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
|
||||
|
||||
return html`
|
||||
<button
|
||||
type="button"
|
||||
class=${['storybook-button', `storybook-button--${size || 'medium'}`, mode].join(' ')}
|
||||
style=${backgroundColor && { backgroundColor }}
|
||||
style=${styleMap({ backgroundColor })}
|
||||
@click=${onClick}
|
||||
>
|
||||
${label}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { html } from 'lit-html';
|
||||
import { styleMap } from 'lit-html/directives/style-map';
|
||||
import './button.css';
|
||||
|
||||
export interface ButtonProps {
|
||||
@ -26,14 +27,14 @@ export interface ButtonProps {
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
*/
|
||||
export const Button = ({ primary, backgroundColor, size, label, onClick }: ButtonProps) => {
|
||||
export const Button = ({ primary, backgroundColor = null, size, label, onClick }: ButtonProps) => {
|
||||
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
|
||||
|
||||
return html`
|
||||
<button
|
||||
type="button"
|
||||
class=${['storybook-button', `storybook-button--${size || 'medium'}`, mode].join(' ')}
|
||||
style=${backgroundColor && { backgroundColor }}
|
||||
style=${styleMap({ backgroundColor })}
|
||||
@click=${onClick}
|
||||
>
|
||||
${label}
|
||||
|
114
lib/client-api/src/inferControls.test.ts
Normal file
114
lib/client-api/src/inferControls.test.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { StoryContext } from '@storybook/addons';
|
||||
import { logger } from '@storybook/client-logger';
|
||||
import { inferControls } from './inferControls';
|
||||
|
||||
const getStoryContext = (customParams = {}): StoryContext => ({
|
||||
id: '',
|
||||
kind: '',
|
||||
name: '',
|
||||
args: {},
|
||||
globals: {},
|
||||
argTypes: {},
|
||||
parameters: {
|
||||
argTypes: {
|
||||
label: { control: 'text' },
|
||||
labelName: { control: 'text' },
|
||||
borderWidth: { control: { type: 'number', min: 0, max: 10 } },
|
||||
},
|
||||
__isArgsStory: true,
|
||||
...customParams,
|
||||
},
|
||||
});
|
||||
|
||||
describe('inferControls', () => {
|
||||
describe('with custom matchers', () => {
|
||||
let warnSpy: jest.SpyInstance;
|
||||
beforeEach(() => {
|
||||
warnSpy = jest.spyOn(logger, 'warn');
|
||||
warnSpy.mockImplementation(() => {});
|
||||
});
|
||||
afterEach(() => {
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should return color type when matching color', () => {
|
||||
// passing a string, should return control type color
|
||||
const inferredControls = inferControls(
|
||||
getStoryContext({
|
||||
argTypes: {
|
||||
background: {
|
||||
type: {
|
||||
name: 'string',
|
||||
value: 'red',
|
||||
},
|
||||
name: 'background',
|
||||
},
|
||||
},
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /background/,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(inferredControls.background.control.type).toEqual('color');
|
||||
});
|
||||
|
||||
it('should return inferred type when matches color but arg is not a string', () => {
|
||||
// passing an object which is unsupported, should infer the type to object
|
||||
const inferredControls = inferControls(
|
||||
getStoryContext({
|
||||
argTypes: {
|
||||
background: {
|
||||
type: {
|
||||
name: 'object',
|
||||
value: {
|
||||
rgb: [255, 255, 0],
|
||||
},
|
||||
},
|
||||
name: 'background',
|
||||
},
|
||||
},
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /background/,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(warnSpy).toHaveBeenCalled();
|
||||
expect(inferredControls.background.control.type).toEqual('object');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return argTypes as is when no exclude or include is passed', () => {
|
||||
const controls = inferControls(getStoryContext());
|
||||
expect(Object.keys(controls)).toEqual(['label', 'labelName', 'borderWidth']);
|
||||
});
|
||||
|
||||
it('should return filtered argTypes when include is passed', () => {
|
||||
const [includeString, includeArray, includeRegex] = [
|
||||
inferControls(getStoryContext({ controls: { include: 'label' } })),
|
||||
inferControls(getStoryContext({ controls: { include: ['label'] } })),
|
||||
inferControls(getStoryContext({ controls: { include: /label*/ } })),
|
||||
];
|
||||
|
||||
expect(Object.keys(includeString)).toEqual(['label', 'labelName']);
|
||||
expect(Object.keys(includeArray)).toEqual(['label']);
|
||||
expect(Object.keys(includeRegex)).toEqual(['label', 'labelName']);
|
||||
});
|
||||
|
||||
it('should return filtered argTypes when exclude is passed', () => {
|
||||
const [excludeString, excludeArray, excludeRegex] = [
|
||||
inferControls(getStoryContext({ controls: { exclude: 'label' } })),
|
||||
inferControls(getStoryContext({ controls: { exclude: ['label'] } })),
|
||||
inferControls(getStoryContext({ controls: { exclude: /label*/ } })),
|
||||
];
|
||||
|
||||
expect(Object.keys(excludeString)).toEqual(['borderWidth']);
|
||||
expect(Object.keys(excludeArray)).toEqual(['labelName', 'borderWidth']);
|
||||
expect(Object.keys(excludeRegex)).toEqual(['borderWidth']);
|
||||
});
|
||||
});
|
@ -1,5 +1,7 @@
|
||||
import mapValues from 'lodash/mapValues';
|
||||
import { ArgType } from '@storybook/addons';
|
||||
import { logger } from '@storybook/client-logger';
|
||||
|
||||
import { SBEnumType, ArgTypesEnhancer } from './types';
|
||||
import { combineParameters } from './parameters';
|
||||
import { filterArgTypes } from './filterArgTypes';
|
||||
@ -17,7 +19,14 @@ const inferControl = (argType: ArgType, name: string, matchers: ControlsMatchers
|
||||
|
||||
// args that end with background or color e.g. iconColor
|
||||
if (matchers.color && matchers.color.test(name)) {
|
||||
return { control: { type: 'color' } };
|
||||
const controlType = typeof argType.type.value;
|
||||
if (controlType === 'string') {
|
||||
return { control: { type: 'color' } };
|
||||
}
|
||||
|
||||
logger.warn(
|
||||
`Addon controls: Control of type color only supports string, received "${controlType}" instead`
|
||||
);
|
||||
}
|
||||
|
||||
// args that end with date e.g. purchaseDate
|
||||
|
Loading…
x
Reference in New Issue
Block a user