Skip to content

Commit

Permalink
Merge pull request #37 from Silic0nS0ldier/next
Browse files Browse the repository at this point in the history
Fix types for `select` with object values
  • Loading branch information
shilman committed Sep 3, 2021
2 parents bf73bd3 + e5f1663 commit 761ffbf
Show file tree
Hide file tree
Showing 18 changed files with 2,336 additions and 10,073 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/tests-unit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Unit tests

on: [push]

jobs:
build:
name: Core Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: "14.x"
cache: yarn
- name: install
run: |
yarn install --immutable
- name: test
run: |
yarn test --runInBand --ci
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ node_modules/
storybook-static/
build-storybook.log
.DS_Store
.env
.env
.cache
yarn-error.log
22 changes: 22 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module.exports = {
cacheDirectory: '.cache/jest',
clearMocks: true,
testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
collectCoverage: false,
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
],
coveragePathIgnorePatterns: [
'/node_modules/',
'/dist/',
'^.*\\.stories\\.[jt]sx?$',
],
snapshotSerializers: ['@emotion/jest'],
coverageDirectory: 'coverage',
setupFilesAfterEnv: ['./scripts/jest.init.js'],
coverageReporters: ['lcov'],
testEnvironment: 'jest-environment-jsdom',
setupFiles: [],
testURL: 'http://localhost',
moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'node'],
};
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"buildBabel": "babel ./src --out-dir ./dist --extensions \".js,.jsx,.ts,.tsx\"",
"buildTsc": "tsc --declaration --emitDeclarationOnly --outDir ./dist",
"build": "concurrently \"npm run buildBabel\" \"npm run buildTsc\"",
"test": "echo \"Error: no test specified\" && exit 1",
"test": "jest",
"storybook": "start-storybook -p 6006",
"start": "concurrently \"npm run storybook -- --no-manager-cache --quiet\" \"npm run build -- --watch\"",
"build-storybook": "build-storybook",
Expand All @@ -48,7 +48,11 @@
"@babel/preset-env": "^7.12.1",
"@babel/preset-react": "^7.12.5",
"@babel/preset-typescript": "^7.13.0",
"@emotion/jest": "^11.3.0",
"@storybook/react": "^6.3.0",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@testing-library/user-event": "^13.2.1",
"@types/escape-html": "^1.0.1",
"@types/lodash": "^4.14.168",
"@types/react-lifecycles-compat": "^3.0.1",
Expand All @@ -58,6 +62,7 @@
"babel-loader": "^8.1.0",
"chalk": "^2.4.2",
"concurrently": "^5.3.0",
"jest": "^27.0.6",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"rimraf": "^3.0.2",
Expand Down
16 changes: 16 additions & 0 deletions scripts/jest.init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// setup file
import '@testing-library/jest-dom';

/**
* Fail tests on console errors and warnings.
*/
const consoleError = global.console.error;
global.console.error = (...args) => {
consoleError(...args);
throw new Error(JSON.stringify(args));
}
const consoleWarn = global.console.warn;
global.console.warn = (...args) => {
consoleWarn(...args);
throw new Error(JSON.stringify(args));
}
24 changes: 24 additions & 0 deletions src/__types__/knob-test-cases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,30 @@ expectKnobOfType<number[]>(
)
);

expectKnobOfType<{}>(
select(
'select with object options',
{
foo: { foo: 'bar' },
bar: { bar: 'foo' },
foobar: { foobar: 'barfoo' },
},
{ foo: 'bar' }
)
);

expectKnobOfType<{}|string[]|string>(
select(
'select with object, array, and string options',
{
foo: { foo: 'bar' },
bar: 'bar',
foobar: ['foo', 'bar'],
},
{ foo: 'bar' }
)
);

/** Object knob */

expectKnobOfType(
Expand Down
253 changes: 253 additions & 0 deletions src/components/Panel.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { STORY_CHANGED } from '@storybook/core-events';
import { TabsState } from '@storybook/components';

import { ThemeProvider, themes, convert } from '@storybook/theming';
import Panel, { DEFAULT_GROUP_ID } from './Panel';
import { CHANGE, SET } from '../shared';
import PropForm from './PropForm';
import { API } from '@storybook/api';

type CreateMocked<Type> = {
[Property in keyof Type]: jest.Mock & Type[Property];
};

const createTestApi = (): CreateMocked<Pick<API, 'on' | 'off' | 'emit' | 'getQueryParam' | 'setQueryParams'>> => ({
on: jest.fn(() => () => {}),
off: jest.fn(),
emit: jest.fn(),
getQueryParam: jest.fn(() => undefined),
setQueryParams: jest.fn(),
});

describe('Panel', () => {
it('should subscribe to setKnobs event of channel', () => {
const testApi = createTestApi();
render(
<ThemeProvider theme={convert(themes.light)}>
<Panel api={testApi} active />
</ThemeProvider>
);
expect(testApi.on).toHaveBeenCalledWith(SET, expect.any(Function));
});

it('should subscribe to STORY_CHANGE event', () => {
const testApi = createTestApi();
render(
<ThemeProvider theme={convert(themes.light)}>
<Panel api={testApi} active />
</ThemeProvider>
);

expect(testApi.on.mock.calls).toContainEqual([STORY_CHANGED, expect.any(Function)]);
expect(testApi.on).toHaveBeenCalledWith(SET, expect.any(Function));
});

describe('setKnobs handler', () => {
it('should read url params and set values for existing knobs', () => {
const handlers = {};

const testQueryParams = {
'knob-foo': 'test string',
bar: 'some other string',
};

const testApi = {
on: (e, handler) => {
handlers[e] = handler;
return () => {};
},
off: jest.fn(),
emit: jest.fn(),
getQueryParam: (key) => testQueryParams[key],
setQueryParams: jest.fn(),
};

render(
<ThemeProvider theme={convert(themes.light)}>
<Panel api={testApi} active />
</ThemeProvider>
);
const setKnobsHandler = handlers[SET];

const knobs = {
foo: {
name: 'foo',
value: 'default string',
type: 'text',
},
baz: {
name: 'baz',
value: 'another knob value',
type: 'text',
},
};

setKnobsHandler({ knobs, timestamp: +new Date() });
const knobFromUrl = {
name: 'foo',
value: testQueryParams['knob-foo'],
type: 'text',
};
const e = CHANGE;
expect(testApi.emit).toHaveBeenCalledWith(e, knobFromUrl);
});
});

describe('handleChange()', () => {
it.skip('should set queryParams and emit knobChange event', () => {
const testApi = {
getQueryParam: jest.fn(),
setQueryParams: jest.fn(() => undefined),
on: jest.fn(() => () => {}),
off: jest.fn(),
emit: jest.fn(),
};

render(<Panel api={testApi} active />);

const testChangedKnob = {
name: 'foo',
value: 'changed text',
type: 'text',
};
// todo
// wrapper.instance().handleChange(testChangedKnob);
expect(testApi.emit).toHaveBeenCalledWith(CHANGE, testChangedKnob);

// const paramsChange = { 'knob-foo': 'changed text' };
// expect(testApi.setQueryParams).toHaveBeenCalledWith(paramsChange);
});
});

describe('groups', () => {
const testApi = {
off: jest.fn(),
emit: jest.fn(),
getQueryParam: jest.fn(() => undefined),
setQueryParams: jest.fn(),
on: jest.fn(() => () => {}),
};

it.skip('should have no tabs when there are no groupIds', () => {
// Unfortunately, a shallow render will not invoke the render() function of the groups --
// it thinks they are unnamed function components (what they effectively are anyway).
//
// We have to do a full mount.

const root = render(
<ThemeProvider theme={convert(themes.light)}>
<Panel api={testApi} active />
</ThemeProvider>
);

// testApi.on.mock.calls[0][1]({
// knobs: {
// foo: {
// name: 'foo',
// defaultValue: 'test',
// used: true,
// // no groupId
// },
// bar: {
// name: 'bar',
// defaultValue: 'test2',
// used: true,
// // no groupId
// },
// },
// });

// root.rerender();
// const wrapper = root.find(Panel);

// const formWrapper = wrapper.find(PropForm);
// const knobs = formWrapper.map((formInstanceWrapper) => formInstanceWrapper.prop('knobs'));

// expect(knobs).toMatchSnapshot();

// root.unmount();
});

it.skip('should have one tab per groupId when all are defined', () => {
const root = render(
<ThemeProvider theme={convert(themes.light)}>
<Panel api={testApi} active />
</ThemeProvider>
);

// testApi.on.mock.calls[0][1]({
// knobs: {
// foo: {
// name: 'foo',
// defaultValue: 'test',
// used: true,
// groupId: 'foo',
// },
// bar: {
// name: 'bar',
// defaultValue: 'test2',
// used: true,
// groupId: 'bar',
// },
// },
// });

// const wrapper = root.update().find(Panel);

// const titles = wrapper
// .find(TabsState)
// .find('button')
// .map((child) => child.prop('children'));
// expect(titles).toEqual(['foo', 'bar']);

// const knobs = wrapper.find(PropForm);
// // but it should not have its own PropForm in this case
// expect(knobs.length).toEqual(titles.length);
// expect(knobs).toMatchSnapshot();

// root.unmount();
});

it.skip(`the ${DEFAULT_GROUP_ID} tab should have its own additional content when there are knobs both with and without a groupId`, () => {
const root = render(
<ThemeProvider theme={convert(themes.light)}>
<Panel api={testApi} active />
</ThemeProvider>
);

// testApi.on.mock.calls[0][1]({
// knobs: {
// bar: {
// name: 'bar',
// defaultValue: 'test2',
// used: true,
// // no groupId
// },
// foo: {
// name: 'foo',
// defaultValue: 'test',
// used: true,
// groupId: 'foo',
// },
// },
// });

// const wrapper = root.update().find(Panel);

// const titles = wrapper
// .find(TabsState)
// .find('button')
// .map((child) => child.prop('children'));
// expect(titles).toEqual(['foo', DEFAULT_GROUP_ID]);

// const knobs = wrapper.find(PropForm).map((propForm) => propForm.prop('knobs'));
// // there are props with no groupId so Other should also have its own PropForm
// expect(knobs.length).toEqual(titles.length);
// expect(knobs).toMatchSnapshot();

// root.unmount();
});
});
});

0 comments on commit 761ffbf

Please sign in to comment.