diff --git a/.circleci/config.yml b/.circleci/config.yml index 9a590a8826dc..c16e5d54ed5a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -264,7 +264,7 @@ jobs: at: . - run: name: Test - command: yarn test --coverage --runInBand --core + command: yarn test --coverage --w2 --core - persist_to_workspace: root: . paths: diff --git a/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Test.kt b/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Test.kt index f63649efdb25..cb2cae95fd9a 100644 --- a/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Test.kt +++ b/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Test.kt @@ -25,7 +25,7 @@ object OpenSourceProjects_Storybook_Test : BuildType({ set -e -x yarn - yarn test --core --coverage --runInBand --teamcity + yarn test --core --coverage --teamcity --w2 """.trimIndent() dockerImage = "node:%docker.node.version%" } diff --git a/addons/a11y/package.json b/addons/a11y/package.json index 1789eb16384f..d87f02146677 100644 --- a/addons/a11y/package.json +++ b/addons/a11y/package.json @@ -27,6 +27,7 @@ }, "dependencies": { "@storybook/addons": "5.1.0-alpha.9", + "@storybook/api": "5.1.0-alpha.9", "@storybook/client-logger": "5.1.0-alpha.9", "@storybook/components": "5.1.0-alpha.9", "@storybook/core-events": "5.1.0-alpha.9", diff --git a/addons/a11y/src/components/A11YPanel.tsx b/addons/a11y/src/components/A11YPanel.tsx index 6a1944b59625..fb28d83b76ba 100644 --- a/addons/a11y/src/components/A11YPanel.tsx +++ b/addons/a11y/src/components/A11YPanel.tsx @@ -9,6 +9,7 @@ import { AxeResults, Result } from 'axe-core'; import { Report } from './Report'; import { Tabs } from './Tabs'; import { EVENTS } from '../constants'; +import { API } from '@storybook/api'; const Icon = styled(Icons)( { @@ -57,11 +58,7 @@ interface A11YPanelState { interface A11YPanelProps { active: boolean; - api: { - on(event: string, callback: (data: any) => void): void; - off(event: string, callback: (data: any) => void): void; - emit(event: string): void; - }; + api: API; } export class A11YPanel extends Component { diff --git a/addons/a11y/src/components/ColorBlindness.tsx b/addons/a11y/src/components/ColorBlindness.tsx index 1e037ffe5912..3476fbc3fbc1 100644 --- a/addons/a11y/src/components/ColorBlindness.tsx +++ b/addons/a11y/src/components/ColorBlindness.tsx @@ -52,7 +52,11 @@ export class ColorBlindness extends Component this.setState({ expanded: s }); + onVisibilityChange = (s: boolean) => { + if (this.state.expanded !== s) { + this.setState({ expanded: s }); + } + }; render() { const { filter, expanded } = this.state; diff --git a/addons/actions/package.json b/addons/actions/package.json index 7e8462525d52..a8ba8d028e04 100644 --- a/addons/actions/package.json +++ b/addons/actions/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@storybook/addons": "5.1.0-alpha.9", + "@storybook/api": "5.1.0-alpha.9", "@storybook/components": "5.1.0-alpha.9", "@storybook/core-events": "5.1.0-alpha.9", "@storybook/theming": "5.1.0-alpha.9", diff --git a/addons/actions/src/components/ActionLogger/index.tsx b/addons/actions/src/components/ActionLogger/index.tsx index ad3e8611a56d..e2cd0c6fc44a 100644 --- a/addons/actions/src/components/ActionLogger/index.tsx +++ b/addons/actions/src/components/ActionLogger/index.tsx @@ -1,5 +1,5 @@ import React, { Fragment } from 'react'; -import { styled, withTheme } from '@storybook/theming'; +import { styled, withTheme, Theme } from '@storybook/theming'; import Inspector from 'react-inspector'; import { ActionBar, ScrollArea } from '@storybook/components'; @@ -16,7 +16,15 @@ export const Wrapper = styled(({ children, className }) => ( padding: '10px 5px 20px', }); -const ThemedInspector = withTheme(({ theme, ...props }) => ( +interface InspectorProps { + theme: Theme; + sortObjectKeys: boolean; + showNonenumerable: boolean; + name: any; + data: any; +} + +const ThemedInspector = withTheme(({ theme, ...props }: InspectorProps) => ( )); diff --git a/addons/actions/src/containers/ActionLogger/index.tsx b/addons/actions/src/containers/ActionLogger/index.tsx index be59226c96e5..5b77420f0723 100644 --- a/addons/actions/src/containers/ActionLogger/index.tsx +++ b/addons/actions/src/containers/ActionLogger/index.tsx @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import deepEqual from 'fast-deep-equal'; +import { API } from '@storybook/api'; import { STORY_RENDERED } from '@storybook/core-events'; import { ActionLogger as ActionLoggerComponent } from '../../components/ActionLogger'; @@ -9,10 +10,7 @@ import { ActionDisplay } from '../../models'; interface ActionLoggerProps { active: boolean; - api: { - on(event: string, callback: (data: any) => void): void; - off(event: string, callback: (data: any) => void): void; - }; + api: API; } interface ActionLoggerState { diff --git a/addons/backgrounds/package.json b/addons/backgrounds/package.json index dcedf3d3569a..a35fdfb280fc 100644 --- a/addons/backgrounds/package.json +++ b/addons/backgrounds/package.json @@ -26,12 +26,12 @@ }, "dependencies": { "@storybook/addons": "5.1.0-alpha.9", + "@storybook/api": "5.1.0-alpha.9", "@storybook/client-logger": "5.1.0-alpha.9", "@storybook/components": "5.1.0-alpha.9", "@storybook/core-events": "5.1.0-alpha.9", "@storybook/theming": "5.1.0-alpha.9", "core-js": "^2.6.5", - "global": "^4.3.2", "memoizerific": "^1.11.3", "react": "^16.8.4", "util-deprecate": "^1.0.2" diff --git a/addons/backgrounds/src/components/index.ts b/addons/backgrounds/src/components/index.ts deleted file mode 100644 index db2a966bc4fe..000000000000 --- a/addons/backgrounds/src/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ColorIcon'; diff --git a/addons/backgrounds/src/containers/BackgroundSelector.tsx b/addons/backgrounds/src/containers/BackgroundSelector.tsx index fecc6cbdcbf7..4e94e4fb768d 100644 --- a/addons/backgrounds/src/containers/BackgroundSelector.tsx +++ b/addons/backgrounds/src/containers/BackgroundSelector.tsx @@ -1,15 +1,27 @@ import React, { Component, Fragment } from 'react'; import memoize from 'memoizerific'; +import { Combo, Consumer } from '@storybook/api'; import { Global, Theme } from '@storybook/theming'; -import { SET_STORIES } from '@storybook/core-events'; - import { Icons, IconButton, WithTooltip, TooltipLinkList } from '@storybook/components'; import { PARAM_KEY } from '../constants'; import { ColorIcon } from '../components/ColorIcon'; -import { BackgroundConfig, BackgroundSelectorItem } from '../models'; + +interface Item { + id: string; + title: string; + onClick: () => void; + value: string; + right?: any; +} + +interface Input { + name: string; + value: string; + default?: boolean; +} const iframeId = 'storybook-preview-background'; @@ -20,7 +32,7 @@ const createBackgroundSelectorItem = memoize(1000)( value: string, hasSwatch: boolean, change: (arg: { selected: string; expanded: boolean }) => void - ): BackgroundSelectorItem => ({ + ): Item => ({ id: id || name, title: name, onClick: () => { @@ -31,10 +43,7 @@ const createBackgroundSelectorItem = memoize(1000)( }) ); -const getSelectedBackgroundColor = ( - list: BackgroundConfig[], - currentSelectedValue: string -): string => { +const getSelectedBackgroundColor = (list: Input[], currentSelectedValue: string): string => { if (!list.length) { return 'transparent'; } @@ -54,119 +63,96 @@ const getSelectedBackgroundColor = ( return 'transparent'; }; -const getDisplayableState = memoize(10)( - (props: BackgroundToolProps, state: BackgroundToolState, change) => { - const data = props.api.getCurrentStoryData(); - const list: BackgroundConfig[] = (data && data.parameters && data.parameters[PARAM_KEY]) || []; - - const selectedBackgroundColor = getSelectedBackgroundColor(list, state.selected); +const mapper = ({ api, state }: Combo): { items: Input[] } => { + const story = state.storiesHash[state.storyId]; + const list = story ? api.getParameters(story.id, PARAM_KEY) : []; - let availableBackgroundSelectorItems: BackgroundSelectorItem[] = []; + return { items: list || [] }; +}; - if (selectedBackgroundColor !== 'transparent') { - availableBackgroundSelectorItems.push( - createBackgroundSelectorItem('reset', 'Clear background', 'transparent', null, change) - ); - } +const getDisplayedItems = memoize(10)((list: Input[], selected: State['selected'], change) => { + let availableBackgroundSelectorItems: Item[] = []; - if (list.length) { - availableBackgroundSelectorItems = [ - ...availableBackgroundSelectorItems, - ...list.map(({ name, value }) => - createBackgroundSelectorItem(null, name, value, true, change) - ), - ]; - } + if (selected !== 'transparent') { + availableBackgroundSelectorItems.push( + createBackgroundSelectorItem('reset', 'Clear background', 'transparent', null, change) + ); + } - return { - items: availableBackgroundSelectorItems, - selectedBackgroundColor, - }; + if (list.length) { + availableBackgroundSelectorItems = [ + ...availableBackgroundSelectorItems, + ...list.map(({ name, value }) => + createBackgroundSelectorItem(null, name, value, true, change) + ), + ]; } -); -interface BackgroundToolProps { - api: { - on(event: string, callback: (data: any) => void): void; - off(event: string, callback: (data: any) => void): void; - getCurrentStoryData(): any; - }; -} + return availableBackgroundSelectorItems; +}); -interface BackgroundToolState { - items: BackgroundSelectorItem[]; +interface State { selected: string; expanded: boolean; } -export class BackgroundSelector extends Component { - private listener = () => { - this.setState({ selected: null }); +export class BackgroundSelector extends Component<{}, State> { + state: State = { + selected: null, + expanded: false, }; - constructor(props: BackgroundToolProps) { - super(props); - - this.state = { - items: [], - selected: null, - expanded: false, - }; - } - - componentDidMount() { - const { api } = this.props; - api.on(SET_STORIES, this.listener); - } + change = (args: State) => this.setState(args); - componentWillUnmount() { - const { api } = this.props; - api.off(SET_STORIES, this.listener); - } - - change = (args: { selected: string; expanded: boolean }) => this.setState(args); + onVisibilityChange = (s: boolean) => { + if (this.state.expanded !== s) { + this.setState({ expanded: s }); + } + }; render() { - const { expanded } = this.state; - const { items, selectedBackgroundColor } = getDisplayableState( - this.props, - this.state, - this.change + const { expanded, selected } = this.state; + + return ( + + {({ items }: { items: Input[] }) => { + const selectedBackgroundColor = getSelectedBackgroundColor(items, selected); + const links = getDisplayedItems(items, selectedBackgroundColor, this.change); + + return items.length ? ( + + {selectedBackgroundColor ? ( + ({ + [`#${iframeId}`]: { + background: + selectedBackgroundColor === 'transparent' + ? theme.background.content + : selectedBackgroundColor, + }, + })} + /> + ) : null} + } + closeOnClick + > + + + + + + ) : null; + }} + ); - - return items.length ? ( - - {selectedBackgroundColor ? ( - ({ - [`#${iframeId}`]: { - background: - selectedBackgroundColor === 'transparent' - ? theme.background.content - : selectedBackgroundColor, - }, - })} - /> - ) : null} - - this.setState({ expanded: newVisibility }) - } - tooltip={} - closeOnClick - > - - - - - - ) : null; } } diff --git a/addons/backgrounds/src/containers/index.ts b/addons/backgrounds/src/containers/index.ts deleted file mode 100644 index 87f68e7c7ae1..000000000000 --- a/addons/backgrounds/src/containers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './BackgroundSelector'; diff --git a/addons/backgrounds/src/models/BackgroundConfig.ts b/addons/backgrounds/src/models/BackgroundConfig.ts deleted file mode 100644 index f48826281bd5..000000000000 --- a/addons/backgrounds/src/models/BackgroundConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface BackgroundConfig { - name: string; - value: string; - default?: boolean; -} diff --git a/addons/backgrounds/src/models/BackgroundSelectorItem.ts b/addons/backgrounds/src/models/BackgroundSelectorItem.ts deleted file mode 100644 index fdc35e55c10a..000000000000 --- a/addons/backgrounds/src/models/BackgroundSelectorItem.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface BackgroundSelectorItem { - id: string; - title: string; - onClick: () => void; - value: string; - right?: any; -} diff --git a/addons/backgrounds/src/models/index.ts b/addons/backgrounds/src/models/index.ts deleted file mode 100644 index dc3676a463d3..000000000000 --- a/addons/backgrounds/src/models/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './BackgroundConfig'; -export * from './BackgroundSelectorItem'; diff --git a/addons/backgrounds/src/register.tsx b/addons/backgrounds/src/register.tsx index d592fdd43ef0..43d43dcae1c9 100644 --- a/addons/backgrounds/src/register.tsx +++ b/addons/backgrounds/src/register.tsx @@ -2,13 +2,13 @@ import React from 'react'; import { addons, types } from '@storybook/addons'; import { ADDON_ID } from './constants'; -import { BackgroundSelector } from './containers'; +import { BackgroundSelector } from './containers/BackgroundSelector'; -addons.register(ADDON_ID, api => { +addons.register(ADDON_ID, () => { addons.add(ADDON_ID, { title: 'Backgrounds', type: types.TOOL, match: ({ viewMode }) => viewMode === 'story', - render: () => , + render: () => , }); }); diff --git a/addons/cssresources/package.json b/addons/cssresources/package.json index b4cdb3283784..3a4e6d90eba6 100644 --- a/addons/cssresources/package.json +++ b/addons/cssresources/package.json @@ -26,6 +26,7 @@ }, "dependencies": { "@storybook/addons": "5.1.0-alpha.9", + "@storybook/api": "5.1.0-alpha.9", "@storybook/components": "5.1.0-alpha.9", "@storybook/core-events": "5.1.0-alpha.9", "core-js": "^2.6.5", diff --git a/addons/cssresources/src/css-resource-panel.tsx b/addons/cssresources/src/css-resource-panel.tsx index f1ba6c8618ff..a3aaca1aaec4 100644 --- a/addons/cssresources/src/css-resource-panel.tsx +++ b/addons/cssresources/src/css-resource-panel.tsx @@ -1,23 +1,17 @@ import React, { Component, Fragment } from 'react'; import { SyntaxHighlighter } from '@storybook/components'; -import Eventtypes, { STORY_RENDERED } from '@storybook/core-events'; +import { STORY_RENDERED } from '@storybook/core-events'; +import { API } from '@storybook/api'; import { EVENTS, PARAM_KEY } from './constants'; import { CssResource } from './CssResource'; -interface CssResourcePanelProps { +interface Props { active: boolean; - api: { - emit(event: any, data: any): void; - on(event: Eventtypes, callback: (data: any) => void): void; - off(event: Eventtypes, callback: (data: any) => void): void; - getQueryParam(): void; - getParameters(id: string, paramKey: string): any; - setQueryParams(): void; - }; + api: API; } -interface CssResourcePanelState { +interface State { currentStoryId: string; list: CssResource[]; } @@ -26,8 +20,8 @@ interface CssResourceLookup { [key: string]: CssResource; } -export class CssResourcePanel extends Component { - constructor(props: CssResourcePanelProps) { +export class CssResourcePanel extends Component { + constructor(props: Props) { super(props); this.state = { diff --git a/addons/events/src/components/Panel.js b/addons/events/src/components/Panel.js index d726a7a1cdaa..4c37e0727a50 100644 --- a/addons/events/src/components/Panel.js +++ b/addons/events/src/components/Panel.js @@ -16,10 +16,10 @@ const Wrapper = styled.div({ export default class EventsPanel extends Component { static propTypes = { active: PropTypes.bool.isRequired, - channel: PropTypes.shape({ - on: PropTypes.func, + api: PropTypes.shape({ emit: PropTypes.func, - removeListener: PropTypes.func, + off: PropTypes.func, + on: PropTypes.func, }).isRequired, }; @@ -28,15 +28,15 @@ export default class EventsPanel extends Component { }; componentDidMount() { - const { channel } = this.props; + const { api } = this.props; - channel.on(EVENTS.ADD, this.onAdd); + api.on(EVENTS.ADD, this.onAdd); } componentWillUnmount() { - const { channel } = this.props; + const { api } = this.props; - channel.removeListener(EVENTS.ADD, this.onAdd); + api.off(EVENTS.ADD, this.onAdd); } onAdd = events => { @@ -44,9 +44,9 @@ export default class EventsPanel extends Component { }; onEmit = event => { - const { channel } = this.props; + const { api } = this.props; - channel.emit(EVENTS.EMIT, event); + api.emit(EVENTS.EMIT, event); }; render() { diff --git a/addons/events/src/manager.js b/addons/events/src/manager.js index 33531330d85b..6d8bea634686 100644 --- a/addons/events/src/manager.js +++ b/addons/events/src/manager.js @@ -5,12 +5,11 @@ import Panel from './components/Panel'; import { ADDON_ID, PANEL_ID } from './constants'; export function register() { - addons.register(ADDON_ID, () => { - const channel = addons.getChannel(); + addons.register(ADDON_ID, api => { addons.addPanel(PANEL_ID, { title: 'Events', // eslint-disable-next-line react/prop-types - render: ({ active, key }) => , + render: ({ active, key }) => , }); }); } diff --git a/addons/info/src/__snapshots__/index.test.js.snap b/addons/info/src/__snapshots__/index.test.js.snap index 836ee6c3a6a0..5f0d0c5235ce 100644 --- a/addons/info/src/__snapshots__/index.test.js.snap +++ b/addons/info/src/__snapshots__/index.test.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`addon Info should render and external markdown 1`] = ` - + and external markdown 1`] = ` - + `; exports[`addon Info should render and markdown 1`] = ` - + - + `; exports[`addon Info should render component description if story kind matches component 1`] = ` diff --git a/addons/links/package.json b/addons/links/package.json index 222ba4ac5be1..6586f0c5eb11 100644 --- a/addons/links/package.json +++ b/addons/links/package.json @@ -24,6 +24,7 @@ "dependencies": { "@storybook/addons": "5.1.0-alpha.9", "@storybook/core-events": "5.1.0-alpha.9", + "@storybook/router": "5.1.0-alpha.9", "common-tags": "^1.8.0", "core-js": "^2.6.5", "global": "^4.3.2", diff --git a/addons/links/src/constants.js b/addons/links/src/constants.js index 8cd994edd00a..67a313cb314d 100644 --- a/addons/links/src/constants.js +++ b/addons/links/src/constants.js @@ -2,6 +2,4 @@ export const ADDON_ID = 'storybook/links'; export default { NAVIGATE: `${ADDON_ID}/navigate`, - REQUEST: `${ADDON_ID}/request`, - RECEIVE: `${ADDON_ID}/receive`, }; diff --git a/addons/links/src/preview.js b/addons/links/src/preview.js index 816426e449d5..f20f7b1621db 100644 --- a/addons/links/src/preview.js +++ b/addons/links/src/preview.js @@ -2,8 +2,7 @@ import { document } from 'global'; import qs from 'qs'; import addons from '@storybook/addons'; import { SELECT_STORY, STORY_CHANGED } from '@storybook/core-events'; - -import EVENTS from './constants'; +import { toId } from '@storybook/router/utils'; export const navigate = params => addons.getChannel().emit(SELECT_STORY, params); const generateUrl = id => { @@ -27,9 +26,7 @@ export const linkTo = (kind, story) => (...args) => { export const hrefTo = (kind, name) => new Promise(resolve => { - const channel = addons.getChannel(); - channel.once(EVENTS.RECEIVE, id => resolve(generateUrl(id))); - channel.emit(EVENTS.REQUEST, { kind, name }); + resolve(generateUrl(toId(kind, name))); }); const linksListener = e => { diff --git a/addons/links/src/preview.test.js b/addons/links/src/preview.test.js index 357fe203390e..2f9ba57677f6 100644 --- a/addons/links/src/preview.test.js +++ b/addons/links/src/preview.test.js @@ -1,32 +1,14 @@ import addons from '@storybook/addons'; import { SELECT_STORY } from '@storybook/core-events'; import { linkTo, hrefTo } from './preview'; -import EVENTS from './constants'; jest.mock('@storybook/addons'); export const mockChannel = () => { - let cb; return { - emit(id, payload) { - if (id === EVENTS.REQUEST) { - cb( - Object.values(payload) - .map(item => item.toString().toLowerCase()) - .join('-') - ); - } - }, - on(id, callback) { - if (id === EVENTS.RECEIVE) { - cb = callback; - } - }, - once(id, callback) { - if (id === EVENTS.RECEIVE) { - cb = callback; - } - }, + emit: jest.fn(), + on: jest.fn(), + once: jest.fn(), }; }; @@ -64,11 +46,8 @@ describe('preview', () => { describe('hrefTo()', () => { it('should return promise resolved with story href', async () => { - const channel = mockChannel(); - addons.getChannel.mockReturnValue(channel); - const href = await hrefTo('kind', 'name'); - expect(href).toContain('?id=kind-name'); + expect(href).toContain('?id=kind--name'); }); }); }); diff --git a/addons/links/src/react/components/__snapshots__/link.test.js.snap b/addons/links/src/react/components/__snapshots__/link.test.js.snap index fe0328e9d490..a977ec574aa2 100644 --- a/addons/links/src/react/components/__snapshots__/link.test.js.snap +++ b/addons/links/src/react/components/__snapshots__/link.test.js.snap @@ -2,7 +2,7 @@ exports[`LinkTo render should render a link 1`] = ` `; diff --git a/addons/notes/package.json b/addons/notes/package.json index c6067aeb86b5..954bf2f9bde1 100644 --- a/addons/notes/package.json +++ b/addons/notes/package.json @@ -24,6 +24,7 @@ }, "dependencies": { "@storybook/addons": "5.1.0-alpha.9", + "@storybook/api": "5.1.0-alpha.9", "@storybook/client-logger": "5.1.0-alpha.9", "@storybook/components": "5.1.0-alpha.9", "@storybook/core-events": "5.1.0-alpha.9", diff --git a/addons/notes/src/Panel.tsx b/addons/notes/src/Panel.tsx index 0faade462daa..d996a57e1ec6 100644 --- a/addons/notes/src/Panel.tsx +++ b/addons/notes/src/Panel.tsx @@ -1,6 +1,6 @@ -import * as React from 'react'; -import * as PropTypes from 'prop-types'; +import React, { ReactElement, Component, Fragment, ReactNode } from 'react'; import { types } from '@storybook/addons'; +import { API, Consumer, Combo } from '@storybook/api'; import { styled } from '@storybook/theming'; import { STORY_RENDERED } from '@storybook/core-events'; @@ -13,7 +13,7 @@ import { import Giphy from './giphy'; import Markdown from 'markdown-to-jsx'; -import { PARAM_KEY, API, Parameters } from './shared'; +import { PARAM_KEY, Parameters } from './shared'; const Panel = styled.div({ padding: '3rem 40px', @@ -28,10 +28,6 @@ interface Props { api: API; } -interface NotesPanelState { - value?: string; -} - function read(param: Parameters | undefined): string | undefined { if (!param) { return undefined; @@ -46,16 +42,25 @@ function read(param: Parameters | undefined): string | undefined { } } -export const SyntaxHighlighter = (props: any) => { +interface SyntaxHighlighterProps { + className?: string; + children: ReactElement; + [key: string]: any; +} +export const SyntaxHighlighter = ({ className, children, ...props }: SyntaxHighlighterProps) => { // markdown-to-jsx does not add className to inline code - if (props.className === undefined) { - return {props.children}; + if (typeof className !== 'string') { + return {children}; } // className: "lang-jsx" - const language = props.className.split('-'); - return ; + const language = className.split('-'); + return ( + {children} + ); }; +// use our SyntaxHighlighter component in place of a element when +// converting markdown to react elements const defaultOptions = { overrides: { code: SyntaxHighlighter, @@ -65,87 +70,61 @@ const defaultOptions = { }, }; -export default class NotesPanel extends React.Component { - static propTypes = { - active: PropTypes.bool.isRequired, - api: PropTypes.shape({ - on: PropTypes.func, - off: PropTypes.func, - emit: PropTypes.func, - - getParameters: PropTypes.func, - }).isRequired, +interface Overrides { + overrides: { + [type: string]: ReactNode; }; - - readonly state: NotesPanelState = { - value: '', +} +type Options = typeof defaultOptions & Overrides; + +const mapper = ({ state, api }: Combo): { value?: string; options: Options } => { + const extraElements = Object.entries(api.getElements(types.NOTES_ELEMENT)).reduce( + (acc, [k, v]) => ({ ...acc, [k]: v.render }), + {} + ); + const options = { + ...defaultOptions, + overrides: { ...defaultOptions.overrides, ...extraElements }, }; - mounted: boolean; + const story = state.storiesHash[state.storyId]; + const value = read(story ? api.getParameters(story.id, PARAM_KEY) : undefined); - // use our SyntaxHighlighter component in place of a element when - // converting markdown to react elements - - componentDidMount() { - const { api } = this.props; - api.on(STORY_RENDERED, this.onStoryChange); - } + return { options, value }; +}; - componentWillUnmount() { - const { api } = this.props; - api.off(STORY_RENDERED, this.onStoryChange); +const NotesPanel = ({ active }: Props) => { + if (!active) { + return null; } - onStoryChange = (id: string) => { - const { api } = this.props; - const params = api.getParameters(id, PARAM_KEY); - - const value = read(params); - if (value) { - this.setState({ value }); - } else { - this.setState({ value: undefined }); - } - }; - - render() { - const { active, api } = this.props; - const { value } = this.state; - - if (!active) { - return null; - } - - // TODO: memoize - const extraElements = Object.entries(api.getElements(types.NOTES_ELEMENT)).reduce( - (acc, [k, v]) => ({ ...acc, [k]: v.render }), - {} - ); - const options = { - ...defaultOptions, - overrides: { ...defaultOptions.overrides, ...extraElements }, - }; + return ( + + {({ options, value }: { options: Options; value?: string }) => { + return value ? ( + + + {value} + + + ) : ( + + No notes yet + + Learn how to{' '} + + document components in Markdown + + + + ); + }} + + ); +}; - return value ? ( - - - {value} - - - ) : ( - - No notes yet - - Learn how to{' '} - - document components in Markdown - - - - ); - } -} +export default NotesPanel; diff --git a/addons/notes/src/giphy.tsx b/addons/notes/src/giphy.tsx index b51f92bc7f65..ce6e5b065831 100644 --- a/addons/notes/src/giphy.tsx +++ b/addons/notes/src/giphy.tsx @@ -1,5 +1,4 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { logger } from '@storybook/client-logger'; diff --git a/addons/notes/src/register.tsx b/addons/notes/src/register.tsx index 81ecd20e5d9d..8effa1716de8 100644 --- a/addons/notes/src/register.tsx +++ b/addons/notes/src/register.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; import addons, { types } from '@storybook/addons'; -import { ADDON_ID, PANEL_ID, API } from './shared'; +import { ADDON_ID, PANEL_ID } from './shared'; // TODO: fix eslint in tslint (igor said he fixed it, should ask him) import Panel from './Panel'; -addons.register(ADDON_ID, (api: API) => { +addons.register(ADDON_ID, api => { addons.add(PANEL_ID, { type: types.TAB, title: 'Notes', diff --git a/addons/notes/src/shared.ts b/addons/notes/src/shared.ts index c3afe553f00a..82f5a0cb26d5 100644 --- a/addons/notes/src/shared.ts +++ b/addons/notes/src/shared.ts @@ -4,23 +4,6 @@ export const ADDON_ID = 'storybooks/notes'; export const PANEL_ID = `${ADDON_ID}/panel`; export const PARAM_KEY = `notes`; -// TODO: this should come from some core package? -export interface API { - on(event: string, callback: (...args: any) => any): void; - off(event: string, callback: (...args: any) => any): void; - emit(event: string, callback: (...args: any) => any): void; - - getParameters(id: string, scope?: string): undefined | Parameters; - getElements( - type: string - ): { - [id: string]: { - id: string; - render: () => ReactElement; - }; - }; -} - interface TextParameter { text: string; } diff --git a/addons/storyshots/storyshots-puppeteer/package.json b/addons/storyshots/storyshots-puppeteer/package.json index c99da3c51c50..bcd21c603c1d 100644 --- a/addons/storyshots/storyshots-puppeteer/package.json +++ b/addons/storyshots/storyshots-puppeteer/package.json @@ -30,7 +30,7 @@ "regenerator-runtime": "^0.12.1" }, "peerDependencies": { - "@storybook/addon-storyshots": "5.1.0-alpha.6" + "@storybook/addon-storyshots": "5.1.0-alpha.9" }, "publishConfig": { "access": "public" diff --git a/app/react-native-server/package.json b/app/react-native-server/package.json index a6b308582005..f2beb5b1c75d 100644 --- a/app/react-native-server/package.json +++ b/app/react-native-server/package.json @@ -25,6 +25,7 @@ }, "dependencies": { "@storybook/addons": "5.1.0-alpha.9", + "@storybook/api": "5.1.0-alpha.9", "@storybook/channel-websocket": "5.1.0-alpha.9", "@storybook/core": "5.1.0-alpha.9", "@storybook/core-events": "5.1.0-alpha.9", diff --git a/app/react-native-server/src/client/manager/provider.js b/app/react-native-server/src/client/manager/provider.js index 3fbd3c4d6c4e..08a93bcbf6c6 100644 --- a/app/react-native-server/src/client/manager/provider.js +++ b/app/react-native-server/src/client/manager/provider.js @@ -1,4 +1,5 @@ import React from 'react'; +import { Consumer } from '@storybook/api'; import { Provider } from '@storybook/ui'; import createChannel from '@storybook/channel-websocket'; import addons from '@storybook/addons'; @@ -6,54 +7,70 @@ import Events from '@storybook/core-events'; import uuid from 'uuid'; import PreviewHelp from './components/PreviewHelp'; +const mapper = ({ state, api }) => ({ + api, + storiesHash: state.storiesHash, + storyId: state.storyId, +}); + export default class ReactProvider extends Provider { constructor({ url: domain, options }) { super(); - this.options = options; - this.selection = null; const { secured, host, port } = options; const websocketType = secured ? 'wss' : 'ws'; let url = `${websocketType}://${domain}`; + if (options.manualId) { this.pairedId = uuid(); url += `/pairedId=${this.pairedId}`; } - if (!this.channel) { - this.channel = createChannel({ url }); - addons.setChannel(this.channel); + const channel = this.channel || createChannel({ url }); - this.channel.emit(Events.CHANNEL_CREATED, { - pairedId: this.pairedId, - secured, - host, - port, - }); - } + addons.setChannel(channel); + channel.emit(Events.CHANNEL_CREATED, { + host, + pairedId: this.pairedId, + port, + secured, + }); + + this.addons = addons; + this.channel = channel; + this.options = options; + this.selection = null; } getElements(type) { return addons.getElements(type); } - renderPreview(state, api) { - if (state.storiesHash[state.storyId]) { - const { kind, story } = state.storiesHash[state.storyId]; + renderPreview() { + return ( + + {({ storiesHash, storyId, api }) => { + if (storiesHash[storyId]) { + const { kind, story } = storiesHash[storyId]; - if (!this.selection || this.selection.kind !== kind || this.selection.story !== story) { - this.selection = { kind, story }; - api.emit(Events.SET_CURRENT_STORY, { kind, story }); - // FIXME: getPreview not implemented yet. - if (addons.getPreview) { - const renderPreview = addons.getPreview(); - if (renderPreview) { - return renderPreview(kind, story); + if (!this.selection || this.selection.kind !== kind || this.selection.story !== story) { + this.selection = { kind, story }; + // TODO: isn't this event sent twice now? + api.emit(Events.SET_CURRENT_STORY, { kind, story }); + } + + // FIXME: getPreview not implemented yet. + if (addons.getPreview) { + const renderPreview = addons.getPreview(); + if (renderPreview) { + return renderPreview(kind, story); + } + } } - } - } - } - return ; + return ; + }} + + ); } handleAPI(api) { diff --git a/docs/package.json b/docs/package.json index 031f6379642b..2cb8c5f9c0f0 100644 --- a/docs/package.json +++ b/docs/package.json @@ -17,10 +17,10 @@ "storybook": "start-storybook -p 9009 -s src/pages" }, "dependencies": { - "@storybook/addon-actions": "5.1.0-alpha.6", - "@storybook/addon-links": "5.1.0-alpha.6", - "@storybook/addons": "5.1.0-alpha.6", - "@storybook/react": "5.1.0-alpha.6", + "@storybook/addon-actions": "5.1.0-alpha.9", + "@storybook/addon-links": "5.1.0-alpha.9", + "@storybook/addons": "5.1.0-alpha.9", + "@storybook/react": "5.1.0-alpha.9", "babel-loader": "^6.4.1", "babel-plugin-styled-components": "^1.10.0", "bootstrap": "^4.3.1", diff --git a/docs/yarn.lock b/docs/yarn.lock index e605aaffec22..de949af787bf 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -1014,15 +1014,15 @@ react-lifecycles-compat "^3.0.4" warning "^3.0.0" -"@storybook/addon-actions@5.1.0-alpha.6": - version "5.1.0-alpha.6" - resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.1.0-alpha.6.tgz#15d3dc481e27ed7ef774b313c061ee10f64593d0" - integrity sha512-4vA6eq+yyfFFDuChr80o9yNrF+H08juA8Em246Kaxfi0mT1XmCUD3zIfb9trVQ2Qc8AWxzmcLkzfA2q1Bn7BeA== - dependencies: - "@storybook/addons" "5.1.0-alpha.6" - "@storybook/components" "5.1.0-alpha.6" - "@storybook/core-events" "5.1.0-alpha.6" - "@storybook/theming" "5.1.0-alpha.6" +"@storybook/addon-actions@5.1.0-alpha.9": + version "5.1.0-alpha.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.1.0-alpha.9.tgz#3a2a9101e053e16f9bf2c3d4eaeb2c11b7dcaece" + integrity sha512-2xGQ4WBV8rOWq6nfuvcUq1p1/OgUZ5Pb38j3MIBxj7/dFWya4J0CrjZYmd/7mXmwMyTLcrlqwprspKkyfFpcqA== + dependencies: + "@storybook/addons" "5.1.0-alpha.9" + "@storybook/components" "5.1.0-alpha.9" + "@storybook/core-events" "5.1.0-alpha.9" + "@storybook/theming" "5.1.0-alpha.9" core-js "^2.6.5" fast-deep-equal "^2.0.1" global "^4.3.2" @@ -1033,57 +1033,57 @@ react-inspector "^2.3.1" uuid "^3.3.2" -"@storybook/addon-links@5.1.0-alpha.6": - version "5.1.0-alpha.6" - resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-5.1.0-alpha.6.tgz#aa8590b782bf7f41e12bec50de32d82a78681263" - integrity sha512-3NOQUuwu1h4wy7p/hNa+nBp9Skt9TkGVjtxb8Jg6NSAzAOFSuhJSilkL88jHmfPYslEQU0D4OVx65BRftpl3Rw== +"@storybook/addon-links@5.1.0-alpha.9": + version "5.1.0-alpha.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-5.1.0-alpha.9.tgz#d5503b5bae813fe4d78fdfe335f081f2ff675965" + integrity sha512-yq0c7eKLItIfDsvWdP8WYNApwnBmOTcYKsC924/TpZ9yZaYBpTuHqoIbh/3c1I0e75grT03eKkRYn/Ya7rAvLw== dependencies: - "@storybook/addons" "5.1.0-alpha.6" - "@storybook/core-events" "5.1.0-alpha.6" + "@storybook/addons" "5.1.0-alpha.9" + "@storybook/core-events" "5.1.0-alpha.9" common-tags "^1.8.0" core-js "^2.6.5" global "^4.3.2" prop-types "^15.7.2" qs "^6.6.0" -"@storybook/addons@5.1.0-alpha.6": - version "5.1.0-alpha.6" - resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.1.0-alpha.6.tgz#f46e94d9aef9e81b0ea93b1eb56ae8773c57b234" - integrity sha512-SiPBE9b2wa3UG3t0tDHs7C62JogUaj/MlhVyvp6bueUrrlyVOmQTd9XaCdWWyEm7R3CcHupgsGTRld3WL7VfLA== +"@storybook/addons@5.1.0-alpha.9": + version "5.1.0-alpha.9" + resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.1.0-alpha.9.tgz#0aed24ba4eb2345f3d2a6429a93463e282dfb9cb" + integrity sha512-B7cBzw+89cuxBDNL14EO+sng8G9RGaQ1reuZJTFB1mopI3nTHipaEhjpEVNGcZtW6DNkiBWN60rV3SDPybUeOg== dependencies: - "@storybook/channels" "5.1.0-alpha.6" - "@storybook/client-logger" "5.1.0-alpha.6" + "@storybook/channels" "5.1.0-alpha.9" + "@storybook/client-logger" "5.1.0-alpha.9" core-js "^2.6.5" global "^4.3.2" util-deprecate "^1.0.2" -"@storybook/channel-postmessage@5.1.0-alpha.6": - version "5.1.0-alpha.6" - resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-5.1.0-alpha.6.tgz#39c3c690463f4b05d277b3aecbef61105287dc3e" - integrity sha512-Q1z8fsBJ43nHdBj5pRtvs9sOZu00UrWy10lSQlpQ/6OYlsRhFABPAaIdRW1TYYd06dfxA0C42kAewXcO7XbOIw== +"@storybook/channel-postmessage@5.1.0-alpha.9": + version "5.1.0-alpha.9" + resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-5.1.0-alpha.9.tgz#9f5ff69db3feaea805f358130e58c220edba10c5" + integrity sha512-5XJ1nE4gb/97EnoCQvj5SkKDuvdXIvlmJ5zprEH5OV0jZn9urRIjBW6HslY+07jKhrCit5mI6R/zxeyAfJoglg== dependencies: - "@storybook/channels" "5.1.0-alpha.6" - "@storybook/client-logger" "5.1.0-alpha.6" + "@storybook/channels" "5.1.0-alpha.9" + "@storybook/client-logger" "5.1.0-alpha.9" core-js "^2.6.5" global "^4.3.2" telejson "^2.1.1" -"@storybook/channels@5.1.0-alpha.6": - version "5.1.0-alpha.6" - resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.1.0-alpha.6.tgz#852b5890dda1996bed0faddfe0b5cebf8938e36a" - integrity sha512-yQtBohHtpy43sZ3t2HF6zmlh79ZuAZ/VzU6SPcQnEVeWrIsopsU4mZ9Jjya87mdhbo08c9EgY7nJ0NClE8uV4A== +"@storybook/channels@5.1.0-alpha.9": + version "5.1.0-alpha.9" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.1.0-alpha.9.tgz#0078e6250cf44d5c7d885565a98807518242731b" + integrity sha512-gJMJTyzutLcF3u69s3XzU+h1+iEEJ8DcP3DC1OqimhhRy8w+pqn+pRSIMUXLYknRyIeXY95/ShUySKogHQECRA== dependencies: core-js "^2.6.5" -"@storybook/client-api@5.1.0-alpha.6": - version "5.1.0-alpha.6" - resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-5.1.0-alpha.6.tgz#0fcc2c77a92f5c9b40eab466fd2cf4b7af7468df" - integrity sha512-gI567POeIOzT5XML8wdbbsv2rVQdqmBth20LYd/jj8TlWBkwNAcZCFjU4wqGSin71drOWvJH7lQwNEbz0KZNVQ== +"@storybook/client-api@5.1.0-alpha.9": + version "5.1.0-alpha.9" + resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-5.1.0-alpha.9.tgz#712d5ea1f85eecb40f9ee630239d2da3860829b6" + integrity sha512-t+Xm0N/pxBQu7LYistVuO/pnwLTqNcKCL45QCe/sYjsB8ONWEw0w7zXrBQZaVAsdzMdAVj4bJUP5s5M1RgK03Q== dependencies: - "@storybook/addons" "5.1.0-alpha.6" - "@storybook/client-logger" "5.1.0-alpha.6" - "@storybook/core-events" "5.1.0-alpha.6" - "@storybook/router" "5.1.0-alpha.6" + "@storybook/addons" "5.1.0-alpha.9" + "@storybook/client-logger" "5.1.0-alpha.9" + "@storybook/core-events" "5.1.0-alpha.9" + "@storybook/router" "5.1.0-alpha.9" common-tags "^1.8.0" eventemitter3 "^3.1.0" global "^4.3.2" @@ -1094,23 +1094,23 @@ memoizerific "^1.11.3" qs "^6.6.0" -"@storybook/client-logger@5.1.0-alpha.6": - version "5.1.0-alpha.6" - resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.1.0-alpha.6.tgz#a6f2b46fec51119f87d2b8d69146cd0cc2eedcd1" - integrity sha512-pU2JfQeybvKCIKcHmvZTFisWzsdFZS4STprhqD97gISRqyqzf06yxAFjtuuIYoX+omRK8C9uOpLmt9CEmXs8ow== +"@storybook/client-logger@5.1.0-alpha.9": + version "5.1.0-alpha.9" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.1.0-alpha.9.tgz#bdefa0a2317ba004bb6332ced591b3e6cf84b393" + integrity sha512-JjjIWmSV+hVF2n7m212sUP1E5HdGgZvWg7k4nn8ZPO87TPSLnskIFjz5eA71NrFLXS2DkYQB8FoXmSEH1Hfa2g== dependencies: core-js "^2.6.5" -"@storybook/components@5.1.0-alpha.6": - version "5.1.0-alpha.6" - resolved "https://registry.yarnpkg.com/@storybook/components/-/components-5.1.0-alpha.6.tgz#7ad8a675a04cd39a50654a6ea03be0d225d80f3a" - integrity sha512-Q8kz0eNDp927iaQ+k3GSNYm1ppzmOORzfXrKwJSYes4yIk0z2fv1pGKCZ4ewp8YbngryGmfICS+knhHkc4mZ7A== +"@storybook/components@5.1.0-alpha.9": + version "5.1.0-alpha.9" + resolved "https://registry.yarnpkg.com/@storybook/components/-/components-5.1.0-alpha.9.tgz#707d07b9c900b5a2cab9805137f3136d74ac365b" + integrity sha512-xU666kuAU2WscjlGdoXfLCPFbxA2JHwWXGZuykHEY1Y9H7QDPH+7Yr+82z2aZTgokvd/ZZAA/ZhmVXFhl1V3VA== dependencies: - "@storybook/addons" "5.1.0-alpha.6" - "@storybook/client-logger" "5.1.0-alpha.6" - "@storybook/core-events" "5.1.0-alpha.6" - "@storybook/router" "5.1.0-alpha.6" - "@storybook/theming" "5.1.0-alpha.6" + "@storybook/addons" "5.1.0-alpha.9" + "@storybook/client-logger" "5.1.0-alpha.9" + "@storybook/core-events" "5.1.0-alpha.9" + "@storybook/router" "5.1.0-alpha.9" + "@storybook/theming" "5.1.0-alpha.9" core-js "^2.6.5" global "^4.3.2" js-beautify "^1.8.9" @@ -1118,7 +1118,7 @@ polished "^3.0.0" prop-types "^15.7.2" react "^16.8.4" - react-dom "^16.8.3" + react-dom "^16.8.4" react-focus-lock "^1.18.3" react-helmet-async "^0.2.0" react-popper-tooltip "^2.8.0" @@ -1127,31 +1127,31 @@ recompose "^0.30.0" simplebar-react "^0.1.4" -"@storybook/core-events@5.1.0-alpha.6": - version "5.1.0-alpha.6" - resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.1.0-alpha.6.tgz#7a01a3d41350d0e95138827869d585251fa131aa" - integrity sha512-KtFTu81Zk6amAVWe08XTqEVDWI11k0HGAGXlmy/tmNoHBbDBRR0kRpZ5HG0AlVYEQuEXrRsOLzbP0F7Dg116CQ== +"@storybook/core-events@5.1.0-alpha.9": + version "5.1.0-alpha.9" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.1.0-alpha.9.tgz#0422c02917ba17deeb6754d64f43019cf9f1a51a" + integrity sha512-12A0XkufJRSHacmuzXOdHx+ON/gbeIr82/8jwCGzikc/nDP7G85kKy7e1WKYTy1pZkxJ17jwturwPnMO17XeSQ== dependencies: core-js "^2.6.5" -"@storybook/core@5.1.0-alpha.6": - version "5.1.0-alpha.6" - resolved "https://registry.yarnpkg.com/@storybook/core/-/core-5.1.0-alpha.6.tgz#b0fbf34744dca811fb668ce3b6fd48918e3acdc3" - integrity sha512-tsss1+d40dLxRpXasOJqZYCm/wBNuOWMDJEeL81Xq8Au6a2lg9seBxYLXUKmAQJ3GxPqeMEWVTsblwD7Ml7a4Q== +"@storybook/core@5.1.0-alpha.9": + version "5.1.0-alpha.9" + resolved "https://registry.yarnpkg.com/@storybook/core/-/core-5.1.0-alpha.9.tgz#1894cf70ca90e98d1cce070a48c0f5066e245840" + integrity sha512-6IxICzguIaPkqn+vEq5qzFB7ovqDUbabd8qpfQZC8v37qp/OEZV7EzVFKpT//cYHusXjvC5Ie1h9wy+irZsXfA== dependencies: "@babel/plugin-proposal-class-properties" "^7.3.3" "@babel/plugin-proposal-object-rest-spread" "^7.3.2" "@babel/plugin-syntax-dynamic-import" "^7.2.0" "@babel/plugin-transform-react-constant-elements" "^7.2.0" "@babel/preset-env" "^7.3.4" - "@storybook/addons" "5.1.0-alpha.6" - "@storybook/channel-postmessage" "5.1.0-alpha.6" - "@storybook/client-api" "5.1.0-alpha.6" - "@storybook/client-logger" "5.1.0-alpha.6" - "@storybook/core-events" "5.1.0-alpha.6" - "@storybook/node-logger" "5.1.0-alpha.6" - "@storybook/router" "5.1.0-alpha.6" - "@storybook/ui" "5.1.0-alpha.6" + "@storybook/addons" "5.1.0-alpha.9" + "@storybook/channel-postmessage" "5.1.0-alpha.9" + "@storybook/client-api" "5.1.0-alpha.9" + "@storybook/client-logger" "5.1.0-alpha.9" + "@storybook/core-events" "5.1.0-alpha.9" + "@storybook/node-logger" "5.1.0-alpha.9" + "@storybook/router" "5.1.0-alpha.9" + "@storybook/ui" "5.1.0-alpha.9" airbnb-js-shims "^1 || ^2" autoprefixer "^9.4.9" babel-plugin-add-react-displayname "^0.0.5" @@ -1203,10 +1203,10 @@ webpack-dev-middleware "^3.6.0" webpack-hot-middleware "^2.24.3" -"@storybook/node-logger@5.1.0-alpha.6": - version "5.1.0-alpha.6" - resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.1.0-alpha.6.tgz#4a0221d7a9a93afb1cf6c03237aa59c655ec414f" - integrity sha512-yx39bU75+pLyYdOwX2BfK1h9iEHkvi5QSi8LE0XPmUHQMXjuB2nUkT5LibK7hllh4mD8dzIPKW9Ic02O3ZXj3Q== +"@storybook/node-logger@5.1.0-alpha.9": + version "5.1.0-alpha.9" + resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.1.0-alpha.9.tgz#bd3e61f49dbd48bcec6cce35ac7dd3e37c52678d" + integrity sha512-1NZneQ4Frb8hACgBDjleVBSxroujW2kM3CAVOnLo0FtkJzT33H58Ch/61ILcybdrBDEfoKtevqHadbkkPY40Hw== dependencies: chalk "^2.4.2" core-js "^2.6.5" @@ -1214,16 +1214,16 @@ pretty-hrtime "^1.0.3" regenerator-runtime "^0.12.1" -"@storybook/react@5.1.0-alpha.6": - version "5.1.0-alpha.6" - resolved "https://registry.yarnpkg.com/@storybook/react/-/react-5.1.0-alpha.6.tgz#b43f5ae905438885d3e782bdf9127675f4602097" - integrity sha512-U3Sp/KMYyTeRMAUxepi+v8MrDi+dq1K5DU2vYV/Yp7QlJpzgfKE3a4AgZVsL1k51sfhjd0VA4OnaBE1VJWoSFw== +"@storybook/react@5.1.0-alpha.9": + version "5.1.0-alpha.9" + resolved "https://registry.yarnpkg.com/@storybook/react/-/react-5.1.0-alpha.9.tgz#93e05b274933a4af58e2efa4d5ccce995d880e18" + integrity sha512-VwR2n+/lAT4qoutJ4D11IoZZWWUo3nvDiEWugt1bXuGB4kWLzftD4oDoQIoG04Kq8IAnwaWAxXQ3sqYwm+m3Sw== dependencies: "@babel/plugin-transform-react-constant-elements" "^7.2.0" "@babel/preset-flow" "^7.0.0" "@babel/preset-react" "^7.0.0" - "@storybook/core" "5.1.0-alpha.6" - "@storybook/node-logger" "5.1.0-alpha.6" + "@storybook/core" "5.1.0-alpha.9" + "@storybook/node-logger" "5.1.0-alpha.9" "@svgr/webpack" "^4.0.3" babel-plugin-named-asset-import "^0.3.1" babel-plugin-react-docgen "^2.0.2" @@ -1239,26 +1239,26 @@ semver "^5.6.0" webpack "^4.29.6" -"@storybook/router@5.1.0-alpha.6": - version "5.1.0-alpha.6" - resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.1.0-alpha.6.tgz#3c1b911e563cfa9b2d627d43807bf4cdf520b8c4" - integrity sha512-es4lyyL68xFSukd7VDFO541NHh9I87xwEZngup5Welvj7MBUrrCpx0BmMH0BBtThBr/gusJLQ0DI6B6B53d94g== +"@storybook/router@5.1.0-alpha.9": + version "5.1.0-alpha.9" + resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.1.0-alpha.9.tgz#027b5db9b71b068ca305aae716f61c5ea4303159" + integrity sha512-cSug0QbxjhjC3pCZkt2GRBXLql2N2q+PaY5T7ns1QPd+v5tMQjiIJvJs4HMRtqCi81cSHTRt4NMqd42U9GNbfA== dependencies: "@reach/router" "^1.2.1" - "@storybook/theming" "5.1.0-alpha.6" + "@storybook/theming" "5.1.0-alpha.9" core-js "^2.6.5" global "^4.3.2" memoizerific "^1.11.3" qs "^6.6.0" -"@storybook/theming@5.1.0-alpha.6": - version "5.1.0-alpha.6" - resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.1.0-alpha.6.tgz#fe7f60234de664660e3a15d8cb77db90b37491cd" - integrity sha512-clHVTXzIbGrF0sC57WpqwQ8dzJPShrHqNJ1fAsocruo2vBtDYlwgm/aoYiLAZVEQzku19V5y1TYVAwcXqcProg== +"@storybook/theming@5.1.0-alpha.9": + version "5.1.0-alpha.9" + resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.1.0-alpha.9.tgz#04e0d46180f313d7ced0c4429bf52cede6e42c9d" + integrity sha512-Q7VV6EtRpOHJ6Qnz22oF34KMLcJIAYVNuYkDDoeEhR6zboD8n2BTS0N0gSXMGrq611BdbIuZKiaytatmQvFdQw== dependencies: "@emotion/core" "^10.0.7" "@emotion/styled" "^10.0.7" - "@storybook/client-logger" "5.1.0-alpha.6" + "@storybook/client-logger" "5.1.0-alpha.9" common-tags "^1.8.0" core-js "^2.6.5" deep-object-diff "^1.1.0" @@ -1271,17 +1271,17 @@ prop-types "^15.7.2" react-inspector "^2.3.1" -"@storybook/ui@5.1.0-alpha.6": - version "5.1.0-alpha.6" - resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-5.1.0-alpha.6.tgz#573d1c64f083f726011dec4eff2a88ea54766727" - integrity sha512-+7VFpqHn+wJISSl0s2mKdGJLkGiIlqjzW+5VsqUd1Bl6EJ0UBsdIZ7rLU0Wj49cYlkHfXSd/BQVUr3Ncocfyzg== - dependencies: - "@storybook/addons" "5.1.0-alpha.6" - "@storybook/client-logger" "5.1.0-alpha.6" - "@storybook/components" "5.1.0-alpha.6" - "@storybook/core-events" "5.1.0-alpha.6" - "@storybook/router" "5.1.0-alpha.6" - "@storybook/theming" "5.1.0-alpha.6" +"@storybook/ui@5.1.0-alpha.9": + version "5.1.0-alpha.9" + resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-5.1.0-alpha.9.tgz#d1bc4e674728a63e0d548b9dd16445e972042d3d" + integrity sha512-t8rDjSayS48fXWv4E34kRrj27v0j3sRJDDg1qg4VvyuV0M8t7S8YTJMN/XyEwwSo80Zst0z7U6uamVnyh0LIBg== + dependencies: + "@storybook/addons" "5.1.0-alpha.9" + "@storybook/client-logger" "5.1.0-alpha.9" + "@storybook/components" "5.1.0-alpha.9" + "@storybook/core-events" "5.1.0-alpha.9" + "@storybook/router" "5.1.0-alpha.9" + "@storybook/theming" "5.1.0-alpha.9" core-js "^2.6.5" fast-deep-equal "^2.0.1" fuzzy-search "^3.0.1" @@ -1296,7 +1296,7 @@ prop-types "^15.7.2" qs "^6.6.0" react "^16.8.4" - react-dom "^16.8.3" + react-dom "^16.8.4" react-draggable "^3.1.1" react-helmet-async "^0.2.0" react-hotkeys "2.0.0-pre4" @@ -12571,7 +12571,7 @@ react-dom@^15.6.0: object-assign "^4.1.0" prop-types "^15.5.10" -react-dom@^16.8.3: +react-dom@^16.8.3, react-dom@^16.8.4: version "16.8.4" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.4.tgz#1061a8e01a2b3b0c8160037441c3bf00a0e3bc48" integrity sha512-Ob2wK7XG2tUDt7ps7LtLzGYYB6DXMCLj0G5fO6WeEICtT4/HdpOi7W/xLzZnR6RCG1tYza60nMdqtxzA8FaPJQ== diff --git a/examples/angular-cli/src/stories/all-knobs.component.ts b/examples/angular-cli/src/stories/all-knobs.component.ts index 4b36a9dea1c9..1f31d79acc95 100644 --- a/examples/angular-cli/src/stories/all-knobs.component.ts +++ b/examples/angular-cli/src/stories/all-knobs.component.ts @@ -1,5 +1,7 @@ import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core'; +const logger = console; + @Component({ selector: 'storybook-simple-knobs-component', template: ` @@ -39,13 +41,13 @@ export class AllKnobsComponent implements OnChanges, OnInit { nice; constructor() { - console.log('constructor'); + logger.debug('constructor'); } ngOnInit(): void { - console.log('on init, user component'); + logger.debug('on init, user component'); } ngOnChanges(changes: SimpleChanges): void { - console.log(changes); + logger.debug(changes); } } diff --git a/examples/official-storybook/tests/__snapshots__/storyshots.test.js.snap b/examples/official-storybook/tests/__snapshots__/storyshots.test.js.snap index d7509e0da185..bea339bf46fb 100644 --- a/examples/official-storybook/tests/__snapshots__/storyshots.test.js.snap +++ b/examples/official-storybook/tests/__snapshots__/storyshots.test.js.snap @@ -7301,7 +7301,7 @@ exports[`Storyshots UI|Settings/ShortcutsScreen default shortcuts 1`] = ` placeholder="Type keys" readonly="" spellcheck="false" - value="⌃ ⇧​ ," + value="CTRL ⇧​ ," /> { diff --git a/jest.config.js b/jest.config.js index 448a752e3625..6efcc84ed187 100644 --- a/jest.config.js +++ b/jest.config.js @@ -35,17 +35,23 @@ module.exports = { 'app/**/*.{js,jsx,ts,tsx}', 'lib/**/*.{js,jsx,ts,tsx}', 'addons/**/*.{js,jsx,ts,tsx}', - '!**/cli/test/**', - '!**/dist/**', - '!**/generators/**', - '!app/**/__mocks__ /', + ], + coveragePathIgnorePatterns: [ + '/node_modules/', + '/cli/test/', + '/dist/', + '/generators/', + '/dll/', + '/__mocks__ /', ], snapshotSerializers: ['jest-emotion', 'enzyme-to-json/serializer'], coverageDirectory: 'coverage', - testEnvironment: 'jsdom', + coverageReporters: ['lcov'], + testEnvironment: 'jest-environment-jsdom-thirteen', setupTestFrameworkScriptFile: './scripts/jest.init.js', setupFiles: ['raf/polyfill'], testURL: 'http://localhost', modulePathIgnorePatterns: ['/dist/.*/__mocks__/'], moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'node'], + watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'], }; diff --git a/lib/addons/package.json b/lib/addons/package.json index 697acf805f63..f1a03793adaa 100644 --- a/lib/addons/package.json +++ b/lib/addons/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@storybook/channels": "5.1.0-alpha.9", + "@storybook/api": "5.1.0-alpha.9", "@storybook/client-logger": "5.1.0-alpha.9", "core-js": "^2.6.5", "global": "^4.3.2", diff --git a/lib/addons/src/index.ts b/lib/addons/src/index.ts index b2b08f1166a5..29793350d628 100644 --- a/lib/addons/src/index.ts +++ b/lib/addons/src/index.ts @@ -2,9 +2,9 @@ import global from 'global'; // tslint:disable-next-line:no-implicit-dependencies import { ReactElement } from 'react'; import { Channel } from '@storybook/channels'; +import { API } from '@storybook/api'; import logger from '@storybook/client-logger'; import { types, Types, isSupportedType } from './types'; -import deprecate from 'util-deprecate'; export interface RenderOptions { active: boolean; @@ -26,14 +26,14 @@ export interface Addon { render: (renderOptions: RenderOptions) => ReactElement; } -export type Loader = (callback: (api: any) => void) => void; +export type Loader = (api: API) => void; -export { types, isSupportedType }; +export { types, Types, isSupportedType }; interface Loaders { [key: string]: Loader; } -interface Collection { +export interface Collection { [key: string]: Addon; } interface Elements { @@ -78,7 +78,7 @@ export class AddonStore { collection[name] = { id: name, ...addon }; }; - register = (name: string, registerCallback: (api: any) => void): void => { + register = (name: string, registerCallback: (api: API) => void): void => { if (this.loaders[name]) { logger.warn(`${name} was loaded twice, this could have bad side-effects`); } @@ -104,4 +104,5 @@ function getAddonsStore(): AddonStore { // prefer import { addons } from '@storybook/addons' over import addons from '@storybook/addons' // // See public_api.ts + export const addons = getAddonsStore(); diff --git a/lib/addons/src/make-decorator.test.ts b/lib/addons/src/make-decorator.test.ts index 82b3cb3a945e..9d7f0d5b25cd 100644 --- a/lib/addons/src/make-decorator.test.ts +++ b/lib/addons/src/make-decorator.test.ts @@ -1,4 +1,3 @@ -import deprecate from 'util-deprecate'; import { makeDecorator, StoryContext, StoryGetter } from './make-decorator'; // Copy & paste from internal api: client-api/src/client_api @@ -11,15 +10,16 @@ export const defaultDecorateStory = (getStory: StoryGetter, decorators: Decorato getStory ); -jest.mock('util-deprecate'); let deprecatedFns: any[] = []; -(deprecate as any).mockImplementation((fn: (...args: any) => any, warning: string) => { - const deprecatedFn = jest.fn(fn); - deprecatedFns.push({ - deprecatedFn, - warning, - }); - return deprecatedFn; +jest.mock('util-deprecate', () => { + return (fn: (...args: any) => any, warning: string) => { + const deprecatedFn = jest.fn(fn); + deprecatedFns.push({ + deprecatedFn, + warning, + }); + return deprecatedFn; + }; }); const baseContext = { diff --git a/lib/api/package.json b/lib/api/package.json new file mode 100644 index 000000000000..4eb8f01b5d69 --- /dev/null +++ b/lib/api/package.json @@ -0,0 +1,49 @@ +{ + "name": "@storybook/api", + "version": "5.1.0-alpha.9", + "description": "Core Storybook API & Context", + "keywords": [ + "storybook" + ], + "homepage": "https://github.com/storybooks/storybook/tree/master/lib/api", + "bugs": { + "url": "https://github.com/storybooks/storybook/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/storybooks/storybook.git" + }, + "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "prepare": "node ./scripts/generateVersion.js && node ../../scripts/prepare.js" + }, + "dependencies": { + "@storybook/channels": "5.1.0-alpha.9", + "@storybook/client-logger": "5.1.0-alpha.9", + "@storybook/core-events": "5.1.0-alpha.9", + "@storybook/router": "5.1.0-alpha.9", + "@storybook/theming": "5.1.0-alpha.9", + "core-js": "^2.6.5", + "fast-deep-equal": "^2.0.1", + "global": "^4.3.2", + "lodash.isequal": "^4.5.0", + "lodash.mergewith": "^4.6.1", + "lodash.pick": "^4.4.0", + "memoizerific": "^1.11.3", + "prop-types": "^15.6.2", + "react": "^16.7.0", + "semver": "^5.6.0", + "telejson": "^2.1.1", + "util-deprecate": "^1.0.2" + }, + "devDependencies": { + "@types/lodash.isequal": "^4.5.3", + "@types/lodash.mergewith": "^4.6.4", + "@types/lodash.pick": "^4.4.4" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/lib/api/scripts/generateVersion.js b/lib/api/scripts/generateVersion.js new file mode 100644 index 000000000000..e8c518bda78d --- /dev/null +++ b/lib/api/scripts/generateVersion.js @@ -0,0 +1,9 @@ +const fs = require('fs'); +const path = require('path'); + +const output = version => `export const version = '${version}';\n`; + +const text = fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8'); +const json = JSON.parse(text); + +fs.writeFileSync(path.join(__dirname, '../src/version.ts'), output(json.version), 'utf8'); diff --git a/lib/api/src/context.ts b/lib/api/src/context.ts new file mode 100644 index 000000000000..41b6225a9cde --- /dev/null +++ b/lib/api/src/context.ts @@ -0,0 +1,4 @@ +import React from 'react'; +import { Combo } from './index'; + +export const createContext = ({ api, state }: Combo) => React.createContext({ api, state }); diff --git a/lib/api/src/index.tsx b/lib/api/src/index.tsx new file mode 100644 index 000000000000..e869a5c4f5f3 --- /dev/null +++ b/lib/api/src/index.tsx @@ -0,0 +1,249 @@ +import React, { ReactElement, Component, useContext } from 'react'; +import memoize from 'memoizerific'; + +import Events from '@storybook/core-events'; +import { RenderData as RouterData } from '@storybook/router'; +import initProviderApi, { SubAPI as ProviderAPI, Provider } from './init-provider-api'; + +import { createContext } from './context'; +import Store from './store'; +import getInitialState from './initial-state'; + +import initAddons, { SubAPI as AddonsAPI } from './modules/addons'; +import initChannel, { SubAPI as ChannelAPI } from './modules/channel'; +import initNotifications, { + SubState as NotificationState, + SubAPI as NotificationAPI, +} from './modules/notifications'; +import initStories, { + SubState as StoriesSubState, + SubAPI as StoriesAPI, + StoriesRaw, +} from './modules/stories'; +import initLayout, { SubState as LayoutSubState, SubAPI as LayoutAPI } from './modules/layout'; +import initShortcuts, { + SubState as ShortcutsSubState, + SubAPI as ShortcutsAPI, +} from './modules/shortcuts'; +import initURL, { QueryParams, SubAPI as UrlAPI } from './modules/url'; +import initVersions, { + SubState as VersionsSubState, + SubAPI as VersionsAPI, +} from './modules/versions'; + +const ManagerContext = createContext({ api: undefined, state: getInitialState({}) }); + +const { STORY_CHANGED, SET_STORIES, SELECT_STORY } = Events; + +export type Module = StoreData & + RouterData & + ProviderData & { mode?: 'production' | 'development' }; + +export type State = Other & + LayoutSubState & + StoriesSubState & + NotificationState & + VersionsSubState & + RouterData & + ShortcutsSubState; + +export type API = AddonsAPI & + ChannelAPI & + ProviderAPI & + StoriesAPI & + LayoutAPI & + NotificationAPI & + ShortcutsAPI & + VersionsAPI & + UrlAPI & + OtherAPI; + +interface OtherAPI { + [key: string]: any; +} +interface Other { + customQueryParams: QueryParams; + + [key: string]: any; +} + +export interface Combo { + api: API; + state: State; +} + +interface ProviderData { + provider: Provider; +} + +interface StoreData { + store: Store; +} + +interface Children { + children: Component | ((props: Combo) => Component); +} + +type StatePartial = Partial; + +export type Props = Children & RouterData & ProviderData; + +class ManagerProvider extends Component { + constructor(props: Props) { + super(props); + const { provider, location, path, viewMode, storyId, navigate } = props; + + const store = new Store({ + getState: () => this.state, + setState: (stateChange: StatePartial, callback) => this.setState(stateChange, callback), + }); + + // Initialize the state to be the initial (persisted) state of the store. + // This gives the modules the chance to read the persisted state, apply their defaults + // and override if necessary + this.state = store.getInitialState(getInitialState({})); + + const apiData = { + navigate, + store, + provider, + location, + path, + viewMode, + storyId, + }; + + this.modules = [ + initChannel, + initAddons, + initLayout, + initNotifications, + initShortcuts, + initStories, + initURL, + initVersions, + ].map(initModule => initModule(apiData)); + + // Create our initial state by combining the initial state of all modules, then overlaying any saved state + const state = getInitialState(...this.modules.map(m => m.state)); + + // Get our API by combining the APIs exported by each module + const combo = Object.assign({ navigate }, ...this.modules.map(m => m.api)); + + const api = initProviderApi({ provider, store, api: combo }); + + api.on(STORY_CHANGED, (id: string) => { + const options = api.getParameters(id, 'options'); + + if (options) { + api.setOptions(options); + } + }); + + api.on(SET_STORIES, (data: { stories: StoriesRaw }) => { + api.setStories(data.stories); + }); + api.on( + SELECT_STORY, + ({ kind, story, ...rest }: { kind: string; story: string; [k: string]: any }) => { + api.selectStory(kind, story, rest); + } + ); + + this.state = state; + this.api = api; + } + + static displayName = 'Manager'; + api: API; + modules: any[]; + + static getDerivedStateFromProps = (props: Props, state: State) => { + if (state.path !== props.path) { + return { + ...state, + location: props.location, + path: props.path, + viewMode: props.viewMode, + storyId: props.storyId, + }; + } + return null; + }; + + componentDidMount() { + // Now every module has had a chance to set its API, call init on each module which gives it + // a chance to do things that call other modules' APIs. + this.modules.forEach(({ init }) => { + if (init) { + init({ api: this.api }); + } + }); + } + + shouldComponentUpdate(nextProps: Props, nextState: State) { + const prevState = this.state; + const prevProps = this.props; + + if (prevState !== nextState) { + return true; + } + if (prevProps.path !== nextProps.path) { + return true; + } + return false; + } + + render() { + const { children } = this.props; + const value = { + state: this.state, + api: this.api, + }; + + return ( + + {typeof children === 'function' ? children(value) : children} + + ); + } +} + +interface ConsumerProps { + filter?: (combo: C) => S; + children: (d: S | C) => ReactElement | null; +} + +interface SubState { + [key: string]: any; +} + +class ManagerConsumer extends Component> { + dataMemory?: (combo: Combo) => SubState; + + constructor(props: ConsumerProps) { + super(props); + this.dataMemory = props.filter ? memoize(10)(props.filter) : null; + } + + render() { + const { children } = this.props; + + return ( + + {d => { + const data = this.dataMemory ? this.dataMemory(d) : d; + + return children(data); + }} + + ); + } +} + +export function useStorybookState(): State { + const { state } = useContext(ManagerContext); + return state; +} + +export { ManagerConsumer as Consumer, ManagerProvider as Provider }; diff --git a/lib/api/src/init-provider-api.ts b/lib/api/src/init-provider-api.ts new file mode 100644 index 000000000000..f3a8e1a864c9 --- /dev/null +++ b/lib/api/src/init-provider-api.ts @@ -0,0 +1,26 @@ +import { ReactElement } from 'react'; +import { Channel } from '@storybook/channels'; + +import { API } from './index'; +import Store from './store'; + +export interface Provider { + channel?: Channel; + renderPreview?: () => ReactElement; + handleAPI(api: API): void; + [key: string]: any; +} + +export interface SubAPI { + renderPreview?: Provider['renderPreview']; +} + +export default ({ provider, api }: { provider: Provider; api: API; store: Store }) => { + provider.handleAPI(api); + + if (provider.renderPreview) { + api.renderPreview = provider.renderPreview; + } + + return api; +}; diff --git a/lib/api/src/initial-state.ts b/lib/api/src/initial-state.ts new file mode 100644 index 000000000000..0dbb1381205e --- /dev/null +++ b/lib/api/src/initial-state.ts @@ -0,0 +1,14 @@ +import merge from './lib/merge'; + +import { State } from './index'; + +interface Addition { + [key: string]: any; +} +type Additions = Addition[]; + +// Returns the initialState of the app +const main = (...additions: Additions): State => + additions.reduce((acc: State, item) => merge(acc, item), {}); + +export default main; diff --git a/lib/api/src/lib/merge.ts b/lib/api/src/lib/merge.ts new file mode 100644 index 000000000000..e56177ce7dac --- /dev/null +++ b/lib/api/src/lib/merge.ts @@ -0,0 +1,23 @@ +import mergeWith from 'lodash.mergewith'; +import isEqual from 'lodash.isequal'; + +import { logger } from '@storybook/client-logger'; + +export default (a: any, b: any) => + mergeWith({}, a, b, (objValue: any, srcValue: any) => { + if (Array.isArray(srcValue) && Array.isArray(objValue)) { + srcValue.forEach(s => { + const existing = objValue.find(o => o === s || isEqual(o, s)); + if (!existing) { + objValue.push(s); + } + }); + + return objValue; + } + if (Array.isArray(objValue)) { + logger.log(['the types mismatch, picking', objValue]); + return objValue; + } + return undefined; + }); diff --git a/lib/ui/src/libs/shortcut.ts b/lib/api/src/lib/shortcut.ts similarity index 86% rename from lib/ui/src/libs/shortcut.ts rename to lib/api/src/lib/shortcut.ts index da691c6ac577..cfcf6314d8e9 100644 --- a/lib/ui/src/libs/shortcut.ts +++ b/lib/api/src/lib/shortcut.ts @@ -1,7 +1,7 @@ import { navigator } from 'global'; // The shortcut is our JSON-ifiable representation of a shortcut combination -type Shortcut = string[]; +import { KeyCollection, Event } from '../modules/shortcuts'; export const isMacLike = () => navigator && navigator.platform ? !!navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i) : false; @@ -14,7 +14,7 @@ export const isShortcutTaken = (arr1: string[], arr2: string[]): boolean => // Map a keyboard event to a keyboard shortcut // NOTE: if we change the fields on the event that we need, we'll need to update the serialization in core/preview/start.js -export const eventToShortcut = (e: KeyboardEvent): Shortcut | null => { +export const eventToShortcut = (e: Event): KeyCollection | null => { // Meta key only doesn't map to a shortcut if (['Meta', 'Alt', 'Control', 'Shift'].includes(e.key)) { return null; @@ -59,7 +59,10 @@ export const eventToShortcut = (e: KeyboardEvent): Shortcut | null => { return keys.length > 0 ? keys : null; }; -export const shortcutMatchesShortcut = (inputShortcut: Shortcut, shortcut: Shortcut): boolean => { +export const shortcutMatchesShortcut = ( + inputShortcut: KeyCollection, + shortcut: KeyCollection +): boolean => { return ( inputShortcut && inputShortcut.length === shortcut.length && @@ -68,7 +71,7 @@ export const shortcutMatchesShortcut = (inputShortcut: Shortcut, shortcut: Short }; // Should this keyboard event trigger this keyboard shortcut? -export const eventMatchesShortcut = (e: KeyboardEvent, shortcut: Shortcut): boolean => { +export const eventMatchesShortcut = (e: Event, shortcut: KeyCollection): boolean => { return shortcutMatchesShortcut(eventToShortcut(e), shortcut); }; @@ -110,6 +113,6 @@ export const keyToSymbol = (key: string): string => { }; // Display the shortcut as a human readable string -export const shortcutToHumanString = (shortcut: Shortcut): string => { +export const shortcutToHumanString = (shortcut: KeyCollection): string => { return shortcut.map(keyToSymbol).join(' '); }; diff --git a/lib/api/src/modules/addons.ts b/lib/api/src/modules/addons.ts new file mode 100644 index 000000000000..8ed4d507faa1 --- /dev/null +++ b/lib/api/src/modules/addons.ts @@ -0,0 +1,80 @@ +import { Module } from '../index'; +import { ReactElement } from 'react'; + +export enum types { + TAB = 'tab', + PANEL = 'panel', + TOOL = 'tool', + PREVIEW = 'preview', + NOTES_ELEMENT = 'notes-element', +} + +export type Types = types | string; +export interface RenderOptions { + active: boolean; + key: string; +} + +export interface RouteOptions { + storyId: string; +} +export interface MatchOptions { + viewMode: string; +} + +export interface Addon { + title: string; + type?: Types; + id?: string; + route?: (routeOptions: RouteOptions) => string; + match?: (matchOptions: MatchOptions) => boolean; + render: (renderOptions: RenderOptions) => ReactElement; +} +export interface Collection { + [key: string]: Addon; +} + +interface Panels { + [id: string]: Addon; +} + +export interface SubAPI { + getElements: (type: Types) => Collection; + getPanels: () => Collection; + getSelectedPanel: () => string; + setSelectedPanel: (panelName: string) => void; +} + +export function ensurePanel(panels: Panels, selectedPanel?: string, currentPanel?: string) { + const keys = Object.keys(panels); + + if (keys.indexOf(selectedPanel) >= 0) { + return selectedPanel; + } + + if (keys.length) { + return keys[0]; + } + return currentPanel; +} + +export default ({ provider, store }: Module) => { + const api: SubAPI = { + getElements: type => provider.getElements(type), + getPanels: () => api.getElements(types.PANEL), + getSelectedPanel: () => { + const { selectedPanel } = store.getState(); + return ensurePanel(api.getPanels(), selectedPanel, selectedPanel); + }, + setSelectedPanel: panelName => { + store.setState({ selectedPanel: panelName }, { persistence: 'session' }); + }, + }; + + return { + api, + state: { + selectedPanel: ensurePanel(api.getPanels(), store.getState().selectedPanel), + }, + }; +}; diff --git a/lib/api/src/modules/channel.ts b/lib/api/src/modules/channel.ts new file mode 100644 index 000000000000..9bcfcacfe2bd --- /dev/null +++ b/lib/api/src/modules/channel.ts @@ -0,0 +1,38 @@ +import deprecate from 'util-deprecate'; +import { STORY_CHANGED } from '@storybook/core-events'; +import { Channel, Listener } from '@storybook/channels'; + +import { Module } from '../index'; + +export interface SubAPI { + getChannel: () => Channel; + on: (type: string, cb: Listener, peer?: boolean) => () => void; + off: (type: string, cb: Listener) => void; + emit: (type: string, ...args: any[]) => void; + once: (type: string, cb: Listener) => void; + onStory: (cb: Listener) => void; +} + +export default ({ provider }: Module) => { + const api: SubAPI = { + getChannel: () => provider.channel, + on: (type, cb, peer = true) => { + if (peer) { + provider.channel.addPeerListener(type, cb); + } else { + provider.channel.addListener(type, cb); + } + + return () => provider.channel.removeListener(type, cb); + }, + off: (type, cb) => provider.channel.removeListener(type, cb), + emit: (type, event) => provider.channel.emit(type, event), + once: (type, event) => provider.channel.once(type, event), + + onStory: deprecate( + (cb: Listener) => api.on(STORY_CHANGED, cb), + 'onStory(...) has been replaced with on(STORY_CHANGED, ...)' + ), + }; + return { api }; +}; diff --git a/lib/ui/src/core/layout.js b/lib/api/src/modules/layout.ts similarity index 60% rename from lib/ui/src/core/layout.js rename to lib/api/src/modules/layout.ts index 434b571c0356..04a97d7344e2 100644 --- a/lib/ui/src/core/layout.js +++ b/lib/api/src/modules/layout.ts @@ -1,31 +1,95 @@ +import { document } from 'global'; import pick from 'lodash.pick'; import deprecate from 'util-deprecate'; import deepEqual from 'fast-deep-equal'; -import { themes } from '@storybook/theming'; -import { document } from 'global'; -import merge from '../libs/merge'; +import { themes, ThemeVars } from '@storybook/theming'; +import merge from '../lib/merge'; +import { State } from '../index'; +import Store from '../store'; + +export type PanelPositions = 'bottom' | 'right'; + +export interface Layout { + isFullscreen: boolean; + showPanel: boolean; + panelPosition: PanelPositions; + showNav: boolean; + isToolshown: boolean; +} + +export interface UI { + name?: string; + url?: string; + enableShortcuts: boolean; + sortStoriesByKind: boolean; + sidebarAnimations: boolean; +} + +export interface SubState { + layout: Layout; + ui: UI; + selectedPanel: string | undefined; + theme: ThemeVars; +} + +export interface SubAPI { + toggleFullscreen: (toggled?: boolean) => void; + togglePanel: (toggled?: boolean) => void; + togglePanelPosition: (position?: PanelPositions) => void; + toggleNav: (toggled?: boolean) => void; + toggleToolbar: (toggled?: boolean) => void; + setOptions: (options: any) => void; +} + +type PartialSubState = Partial; +type PartialThemeVars = Partial; +type PartialLayout = Partial; +type PartialUI = Partial; + +interface Options { + name?: string; + url?: string; + goFullScreen: boolean; + showStoriesPanel: boolean; + showAddonPanel: boolean; + addonPanelInRight: boolean; + theme?: ThemeVars; + selectedPanel?: string; +} + +interface OptionsMap { + [key: string]: string; +} -const deprecatedThemeOptions = { +const deprecatedThemeOptions: { + name: 'brandTitle'; + url: 'brandUrl'; +} = { name: 'brandTitle', url: 'brandUrl', }; -const deprecatedLayoutOptions = { +const deprecatedLayoutOptions: { + goFullScreen: 'isFullscreen'; + showStoriesPanel: 'showNav'; + showAddonPanel: 'showPanel'; + addonPanelInRight: 'panelPosition'; +} = { goFullScreen: 'isFullscreen', showStoriesPanel: 'showNav', showAddonPanel: 'showPanel', addonPanelInRight: 'panelPosition', }; -const deprecationMessage = (optionsMap, prefix) => +const deprecationMessage = (optionsMap: OptionsMap, prefix: string = '') => `The options { ${Object.keys(optionsMap).join(', ')} } are deprecated -- use ${ prefix ? `${prefix}'s` : '' } { ${Object.values(optionsMap).join(', ')} } instead.`; const applyDeprecatedThemeOptions = deprecate( - ({ name, url }) => ({ + ({ name, url }: Options): PartialThemeVars => ({ brandTitle: name, brandUrl: url, brandImage: null, @@ -33,35 +97,39 @@ const applyDeprecatedThemeOptions = deprecate( deprecationMessage(deprecatedThemeOptions) ); -const applyDeprecatedLayoutOptions = deprecate(options => { - const layoutUpdate = {}; +const applyDeprecatedLayoutOptions = deprecate((options: Options): PartialLayout => { + const layoutUpdate: PartialLayout = {}; - ['goFullScreen', 'showStoriesPanel', 'showAddonPanel'].forEach(option => { - if (typeof options[option] !== 'undefined') { - layoutUpdate[deprecatedLayoutOptions[option]] = options[option]; + ['goFullScreen', 'showStoriesPanel', 'showAddonPanel'].forEach( + (option: 'goFullScreen' | 'showStoriesPanel' | 'showAddonPanel') => { + const v = options[option]; + if (typeof v !== 'undefined') { + const key = deprecatedLayoutOptions[option]; + layoutUpdate[key] = v; + } } - }); + ); if (options.addonPanelInRight) { layoutUpdate.panelPosition = 'right'; } return layoutUpdate; }, deprecationMessage(deprecatedLayoutOptions)); -const checkDeprecatedThemeOptions = options => { - if (Object.keys(deprecatedThemeOptions).find(key => !!options[key])) { +const checkDeprecatedThemeOptions = (options: Options) => { + if (Object.values(deprecatedThemeOptions).find(v => !!v)) { return applyDeprecatedThemeOptions(options); } return {}; }; -const checkDeprecatedLayoutOptions = options => { - if (Object.keys(deprecatedLayoutOptions).find(key => typeof options[key] !== 'undefined')) { +const checkDeprecatedLayoutOptions = (options: Options) => { + if (Object.values(deprecatedLayoutOptions).find(v => typeof v !== 'undefined')) { return applyDeprecatedLayoutOptions(options); } return {}; }; -const initial = { +const initial: SubState = { ui: { enableShortcuts: true, sortStoriesByKind: false, @@ -74,6 +142,7 @@ const initial = { showNav: true, panelPosition: 'bottom', }, + selectedPanel: undefined, theme: themes.light, }; @@ -84,10 +153,10 @@ export const focusableUIElements = { }; let hasSetOptions = false; -export default function({ store }) { +export default function({ store }: { store: Store }) { const api = { - toggleFullscreen(toggled) { - return store.setState(state => { + toggleFullscreen(toggled?: boolean) { + return store.setState((state: State) => { const value = typeof toggled !== 'undefined' ? toggled : !state.layout.isFullscreen; return { @@ -99,8 +168,8 @@ export default function({ store }) { }); }, - togglePanel(toggled) { - return store.setState(state => { + togglePanel(toggled?: boolean) { + return store.setState((state: State) => { const value = typeof toggled !== 'undefined' ? toggled : !state.layout.showPanel; return { @@ -112,9 +181,9 @@ export default function({ store }) { }); }, - togglePanelPosition(position) { + togglePanelPosition(position?: 'bottom' | 'right') { if (typeof position !== 'undefined') { - return store.setState(state => ({ + return store.setState((state: State) => ({ layout: { ...state.layout, panelPosition: position, @@ -122,7 +191,7 @@ export default function({ store }) { })); } - return store.setState(state => ({ + return store.setState((state: State) => ({ layout: { ...state.layout, panelPosition: state.layout.panelPosition === 'right' ? 'bottom' : 'right', @@ -130,8 +199,8 @@ export default function({ store }) { })); }, - toggleNav(toggled) { - return store.setState(state => { + toggleNav(toggled?: boolean) { + return store.setState((state: State) => { const value = typeof toggled !== 'undefined' ? toggled : !state.layout.showNav; return { @@ -143,8 +212,8 @@ export default function({ store }) { }); }, - toggleToolbar(toggled) { - return store.setState(state => { + toggleToolbar(toggled?: boolean) { + return store.setState((state: State) => { const value = typeof toggled !== 'undefined' ? toggled : !state.layout.isToolshown; return { @@ -156,7 +225,7 @@ export default function({ store }) { }); }, - focusOnUIElement(elementId) { + focusOnUIElement(elementId?: string) { if (!elementId) { return; } @@ -166,7 +235,7 @@ export default function({ store }) { } }, - setOptions: options => { + setOptions: (options: any) => { // The very first time the user sets their options, we don't consider what is in the store. // At this point in time, what is in the store is what we *persisted*. We did that in order // to avoid a FOUC (e.g. initial rendering the wrong theme while we waited for the stories to load) @@ -193,7 +262,7 @@ export default function({ store }) { ...checkDeprecatedThemeOptions(options), }; - const modification = {}; + const modification: PartialSubState = {}; if (!deepEqual(ui, updatedUi)) { modification.ui = updatedUi; @@ -218,7 +287,6 @@ export default function({ store }) { }; const persisted = pick(store.getState(), 'layout', 'ui', 'selectedPanel', 'theme'); - const state = merge(initial, persisted); - return { api, state }; + return { api, state: merge(initial, persisted) }; } diff --git a/lib/api/src/modules/notifications.ts b/lib/api/src/modules/notifications.ts new file mode 100644 index 000000000000..fabcc0312d7d --- /dev/null +++ b/lib/api/src/modules/notifications.ts @@ -0,0 +1,43 @@ +import { Module } from '../index'; + +export interface Notification { + id: string; + onClear?: () => void; +} + +export interface SubState { + notifications: Notification[]; +} + +export interface SubAPI { + addNotification: (notification: Notification) => void; + clearNotification: (id: string) => void; +} + +export default function({ store }: Module) { + const api = { + addNotification: (notification: Notification) => { + // Get rid of it if already exists + api.clearNotification(notification.id); + + const { notifications } = store.getState(); + + store.setState({ notifications: [...notifications, notification] }); + }, + + clearNotification: (id: string) => { + const { notifications } = store.getState(); + + store.setState({ notifications: notifications.filter((n: Notification) => n.id !== id) }); + + const notification = notifications.find((n: Notification) => n.id === id); + if (notification && notification.onClear) { + notification.onClear(); + } + }, + }; + + const state: SubState = { notifications: [] }; + + return { api, state }; +} diff --git a/lib/ui/src/core/shortcuts.js b/lib/api/src/modules/shortcuts.ts similarity index 70% rename from lib/ui/src/core/shortcuts.js rename to lib/api/src/modules/shortcuts.ts index 4f3dc697886a..45029ff2fe68 100644 --- a/lib/ui/src/core/shortcuts.js +++ b/lib/api/src/modules/shortcuts.ts @@ -1,14 +1,56 @@ import { navigator, document } from 'global'; import { PREVIEW_KEYDOWN } from '@storybook/core-events'; -import { shortcutMatchesShortcut, eventToShortcut } from '../libs/shortcut'; +import { Module, API } from '../index'; + +import { shortcutMatchesShortcut, eventToShortcut } from '../lib/shortcut'; import { focusableUIElements } from './layout'; export const isMacLike = () => navigator && navigator.platform ? !!navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i) : false; export const controlOrMetaKey = () => (isMacLike() ? 'meta' : 'control'); -export const defaultShortcuts = Object.freeze({ +export function keys(o: O) { + return Object.keys(o) as Array; +} + +export interface SubState { + shortcuts: Shortcuts; +} + +export interface SubAPI { + getShortcutKeys(): Shortcuts; + setShortcuts(shortcuts: Shortcuts): Promise; + setShortcut(action: Action, value: KeyCollection): Promise; + restoreAllDefaultShortcuts(): Promise; + restoreDefaultShortcut(action: Action): Promise; + handleKeydownEvent(api: API, event: Event): void; + handleShortcutFeature(api: API, feature: Action): void; +} +export type KeyCollection = string[]; + +export interface Shortcuts { + fullScreen: KeyCollection; + togglePanel: KeyCollection; + panelPosition: KeyCollection; + toggleNav: KeyCollection; + toolbar: KeyCollection; + search: KeyCollection; + focusNav: KeyCollection; + focusIframe: KeyCollection; + focusPanel: KeyCollection; + prevComponent: KeyCollection; + nextComponent: KeyCollection; + prevStory: KeyCollection; + nextStory: KeyCollection; + shortcutsPage: KeyCollection; + aboutPage: KeyCollection; + escape: KeyCollection; +} + +export type Action = keyof Shortcuts; + +export const defaultShortcuts: Shortcuts = Object.freeze({ fullScreen: ['F'], togglePanel: ['A'], panelPosition: ['D'], @@ -24,17 +66,26 @@ export const defaultShortcuts = Object.freeze({ nextStory: ['alt', 'ArrowRight'], shortcutsPage: [controlOrMetaKey(), 'shift', ','], aboutPage: [','], - // This one is not customizable - escape: ['escape'], + escape: ['escape'], // This one is not customizable }); -export default function initShortcuts({ store }) { - const api = { +export interface Event extends KeyboardEvent { + target: { + tagName: string; + addEventListener(): void; + removeEventListener(): boolean; + dispatchEvent(event: Event): boolean; + getAttribute(attr: string): string | null; + }; +} + +export default function initShortcuts({ store }: Module) { + const api: SubAPI = { // Getting and setting shortcuts - getShortcutKeys() { + getShortcutKeys(): Shortcuts { return store.getState().shortcuts; }, - async setShortcuts(shortcuts) { + async setShortcuts(shortcuts: Shortcuts) { await store.setState({ shortcuts }, { persistence: 'permanent' }); return shortcuts; }, @@ -55,13 +106,13 @@ export default function initShortcuts({ store }) { handleKeydownEvent(fullApi, event) { const shortcut = eventToShortcut(event); const shortcuts = api.getShortcutKeys(); - const matchedFeature = Object.keys(shortcuts).find(feature => + const actions = keys(shortcuts); + const matchedFeature = actions.find((feature: Action) => shortcutMatchesShortcut(shortcut, shortcuts[feature]) ); if (matchedFeature) { - return api.handleShortcutFeature(fullApi, matchedFeature); + api.handleShortcutFeature(fullApi, matchedFeature); } - return false; }, handleShortcutFeature(fullApi, feature) { @@ -206,17 +257,17 @@ export default function initShortcuts({ store }) { }, }; - const { shortcuts: persistedShortcuts = {} } = store.getState(); - const state = { + const { shortcuts: persistedShortcuts = defaultShortcuts }: SubState = store.getState(); + const state: SubState = { // Any saved shortcuts that are still in our set of defaults - shortcuts: Object.keys(defaultShortcuts).reduce( + shortcuts: keys(defaultShortcuts).reduce( (acc, key) => ({ ...acc, [key]: persistedShortcuts[key] || defaultShortcuts[key] }), - {} + defaultShortcuts ), }; - const init = ({ api: fullApi }) => { - function focusInInput(event) { + const init = ({ api: fullApi }: API) => { + function focusInInput(event: Event) { return ( /input|textarea/i.test(event.target.tagName) || event.target.getAttribute('contenteditable') !== null @@ -224,17 +275,18 @@ export default function initShortcuts({ store }) { } // Listen for keydown events in the manager - document.addEventListener('keydown', event => { + document.addEventListener('keydown', (event: Event) => { if (!focusInInput(event)) { fullApi.handleKeydownEvent(fullApi, event); } }); // Also listen to keydown events sent over the channel - fullApi.on(PREVIEW_KEYDOWN, data => { + fullApi.on(PREVIEW_KEYDOWN, (data: { event: Event }) => { fullApi.handleKeydownEvent(fullApi, data.event); }); }; + const result = { api, state, init }; - return { api, state, init }; + return result; } diff --git a/lib/ui/src/core/stories.js b/lib/api/src/modules/stories.ts similarity index 60% rename from lib/ui/src/core/stories.js rename to lib/api/src/modules/stories.ts index fc37f0d0002b..de9ecf450690 100644 --- a/lib/ui/src/core/stories.js +++ b/lib/api/src/modules/stories.ts @@ -1,14 +1,89 @@ -import { toId, sanitize } from '@storybook/router/utils'; +import { toId, sanitize } from '@storybook/router/dist/utils'; + +import { Module } from '../index'; +import merge from '../lib/merge'; + +type Direction = -1 | 1; +type StoryId = string; +type ParameterName = string; + +type ViewMode = 'story' | 'info' | undefined; + +export interface SubState { + storiesHash: StoriesHash; + storyId: StoryId; + viewMode: ViewMode; + storiesConfigured: boolean; +} + +export interface SubAPI { + storyId: typeof toId; + selectStory: (kindOrId: string, story?: string, obj?: any) => void; + getCurrentStoryData: () => Story | Group; + setStories: (stories: StoriesRaw) => void; + jumpToComponent: (direction: Direction) => void; + jumpToStory: (direction: Direction) => void; + getData: (storyId: StoryId) => Story | Group; + getParameters: (storyId: StoryId, parameterName?: ParameterName) => Story['parameters'] | any; +} + +interface SeparatorOptions { + rootSeparator: string | RegExp; + groupSeparator: string | RegExp; +} + +interface Group { + id: StoryId; + name: string; + children: StoryId[]; + parent: StoryId; + depth: number; + isComponent: boolean; + isRoot: boolean; + isLeaf: boolean; +} + +interface StoryInput { + id: StoryId; + name: string; + kind: string; + children: string[]; + parameters: { + filename: string; + options: { + hierarchyRootSeparator: RegExp; + hierarchySeparator: RegExp; + [key: string]: any; + }; + [parameterName: string]: any; + }; + isLeaf: boolean; +} + +type Story = StoryInput & Group; + +export interface StoriesHash { + [id: string]: Group | Story; +} +export type StoriesList = Array; +export type GroupsList = Group[]; -import merge from '../libs/merge'; +export interface StoriesRaw { + [id: string]: StoryInput; +} const initStoriesApi = ({ store, navigate, storyId: initialStoryId, viewMode: initialViewMode, -}) => { - const getData = storyId => { +}: Module) => { + const isStory = (obj: Group | Story): boolean => { + const story = obj as Story; + return !!(story && story.parameters); + }; + + const getData = (storyId: StoryId) => { const { storiesHash } = store.getState(); return storiesHash[storyId]; @@ -18,16 +93,18 @@ const initStoriesApi = ({ return getData(storyId); }; - const getParameters = (storyId, addon) => { + const getParameters = (storyId: StoryId, parameterName?: ParameterName) => { const data = getData(storyId); - if (!data) { - return null; + + if (isStory(data)) { + const { parameters } = data as Story; + return parameterName ? parameters[parameterName] : parameters; } - const { parameters } = data; - return addon ? parameters[addon] : parameters; + + return null; }; - const jumpToStory = direction => { + const jumpToStory = (direction: Direction) => { const { storiesHash, viewMode, storyId } = store.getState(); // cannot navigate when there's no current selection @@ -47,9 +124,6 @@ const initStoriesApi = ({ if (index === 0 && direction < 0) { return; } - if (direction === 0) { - return; - } const result = lookupList[index + direction]; @@ -58,7 +132,7 @@ const initStoriesApi = ({ } }; - const jumpToComponent = direction => { + const jumpToComponent = (direction: Direction) => { const state = store.getState(); const { storiesHash, viewMode, storyId } = state; @@ -84,16 +158,13 @@ const initStoriesApi = ({ if (index === 0 && direction < 0) { return; } - if (direction === 0) { - return; - } const result = lookupList[index + direction][0]; navigate(`/${viewMode || 'story'}/${result}`); }; - const splitPath = (kind, { rootSeparator, groupSeparator }) => { + const splitPath = (kind: string, { rootSeparator, groupSeparator }: SeparatorOptions) => { const [root, remainder] = kind.split(rootSeparator, 2); const groups = (remainder || kind).split(groupSeparator).filter(i => !!i); @@ -104,17 +175,16 @@ const initStoriesApi = ({ }; }; - const toKey = input => + const toKey = (input: string) => input.replace(/[^a-z0-9]+([a-z0-9])/gi, (...params) => params[1].toUpperCase()); - const toGroup = name => ({ + const toGroup = (name: string) => ({ name, - children: [], id: toKey(name), }); - const setStories = input => { - // This doesn't quite have the right order -- it does not group the top-level keys, see #5518 + const setStories = (input: StoriesRaw) => { + const hash: StoriesHash = {}; const storiesHashOutOfOrder = Object.values(input).reduce((acc, item) => { const { kind, parameters } = item; // FIXME: figure out why parameters is missing when used with react-native-server @@ -133,22 +203,26 @@ const initStoriesApi = ({ .concat(groups) .map(toGroup) // Map a bunch of extra fields onto the groups, collecting the path as we go (thus the reduce) - .reduce((soFar, group, index, original) => { - const { name } = group; - const parent = index > 0 && soFar[index - 1].id; - const id = sanitize(parent ? `${parent}-${name}` : name); - return soFar.concat([ - { + .reduce( + (soFar, group, index, original) => { + const { name } = group; + const parent = index > 0 && soFar[index - 1].id; + const id = sanitize(parent ? `${parent}-${name}` : name); + + const result: Group = { ...group, id, parent, depth: index, + children: [], isComponent: index === original.length - 1, isLeaf: false, isRoot: !!root && index === 0, - }, - ]); - }, []); + }; + return soFar.concat([result]); + }, + [] as GroupsList + ); const paths = [...rootAndGroups.map(g => g.id), item.id]; @@ -162,14 +236,15 @@ const initStoriesApi = ({ }); }); - acc[item.id] = { ...item, parent: rootAndGroups[rootAndGroups.length - 1].id, isLeaf: true }; + const story = { ...item, parent: rootAndGroups[rootAndGroups.length - 1].id, isLeaf: true }; + acc[item.id] = story as Story; return acc; - }, {}); + }, hash); // When adding a group, also add all of its children, depth first - function addItem(acc, item) { - if (!acc[item]) { + function addItem(acc: StoriesHash, item: Story | Group) { + if (!acc[item.id]) { // If we were already inserted as part of a group, that's great. acc[item.id] = item; const { children } = item; @@ -181,24 +256,27 @@ const initStoriesApi = ({ } // Now create storiesHash by reordering the above by group - const storiesHash = Object.values(storiesHashOutOfOrder).reduce(addItem, {}); + const storiesHash: StoriesHash = Object.values(storiesHashOutOfOrder).reduce(addItem, {}); const { storyId, viewMode } = store.getState(); if (!storyId || !storiesHash[storyId]) { // when there's no storyId or the storyId item doesn't exist // we pick the first leaf and navigate - const firstLeaf = Object.values(storiesHash).find(s => !s.children); + const firstLeaf = Object.values(storiesHash).find((s: Story | Group) => !s.children); if (viewMode && firstLeaf) { navigate(`/${viewMode}/${firstLeaf.id}`); } } - store.setState({ storiesHash }); + store.setState({ + storiesHash, + storiesConfigured: true, + }); }; - const selectStory = (kindOrId, story) => { + const selectStory = (kindOrId: string, story?: string) => { const { viewMode = 'story', storyId, storiesHash } = store.getState(); if (!story) { const s = storiesHash[sanitize(kindOrId)]; @@ -229,6 +307,7 @@ const initStoriesApi = ({ storiesHash: {}, storyId: initialStoryId, viewMode: initialViewMode, + storiesConfigured: false, }, }; }; diff --git a/lib/ui/src/core/url.js b/lib/api/src/modules/url.ts similarity index 66% rename from lib/ui/src/core/url.js rename to lib/api/src/modules/url.ts index 91f3806e5714..633468036914 100644 --- a/lib/ui/src/core/url.js +++ b/lib/api/src/modules/url.ts @@ -1,5 +1,16 @@ import { queryFromLocation } from '@storybook/router'; -import { toId } from '@storybook/router/utils'; +import { toId } from '@storybook/router/dist/utils'; + +import { Module } from '../index'; +import { PanelPositions } from './layout'; + +interface Additions { + isFullscreen?: boolean; + showPanel?: boolean; + panelPosition?: PanelPositions; + showNav?: boolean; + selectedPanel?: string; +} // Initialize the state based on the URL. // NOTE: @@ -10,8 +21,8 @@ import { toId } from '@storybook/router/utils'; // - nav: 0/1 -- show or hide the story list // // We also support legacy URLs from storybook <5 -const initialUrlSupport = ({ location, path }, navigate) => { - const addition = {}; +const initialUrlSupport = ({ navigate, location, path }: Module) => { + const addition: Additions = {}; const query = queryFromLocation(location); let selectedPanel; @@ -71,8 +82,24 @@ const initialUrlSupport = ({ location, path }, navigate) => { return { layout: addition, selectedPanel, location, path, customQueryParams }; }; -export default function({ store, navigate, location, path: initialPath }) { - const api = { +export interface QueryParams { + [key: string]: string; +} + +export interface SubAPI { + getQueryParam: (key: string) => string | undefined; + getUrlState: () => { + queryParams: QueryParams; + path: string; + viewMode?: string; + storyId?: string; + url: string; + }; + setQueryParams: (input: QueryParams) => void; +} + +export default function({ store, navigate, location, path: initialPath, ...rest }: Module) { + const api: SubAPI = { getQueryParam: key => { const { customQueryParams } = store.getState(); if (customQueryParams) { @@ -81,8 +108,8 @@ export default function({ store, navigate, location, path: initialPath }) { return undefined; }, getUrlState: () => { - const { path, viewMode, storyId, url } = store.getState(); - const queryParams = api.getQueryParam(); + const { path, viewMode, storyId, url, customQueryParams } = store.getState(); + const queryParams = customQueryParams; return { queryParams, @@ -94,19 +121,23 @@ export default function({ store, navigate, location, path: initialPath }) { }, setQueryParams(input) { const { customQueryParams } = store.getState(); + const queryParams: QueryParams = {}; store.setState({ customQueryParams: { ...customQueryParams, - ...Object.keys(input).reduce((acc, key) => { - if (input[key] !== null) { - acc[key] = input[key]; + ...Object.entries(input).reduce((acc, [key, value]) => { + if (value !== null) { + acc[key] = value; } return acc; - }, {}), + }, queryParams), }, }); }, }; - return { api, state: initialUrlSupport({ location, path: initialPath }, navigate) }; + return { + api, + state: initialUrlSupport({ store, navigate, location, path: initialPath, ...rest }), + }; } diff --git a/lib/ui/src/core/versions.js b/lib/api/src/modules/versions.ts similarity index 68% rename from lib/ui/src/core/versions.js rename to lib/api/src/modules/versions.ts index c5a2d03e3a7a..b9aeb92cf5f8 100644 --- a/lib/ui/src/core/versions.js +++ b/lib/api/src/modules/versions.ts @@ -1,21 +1,48 @@ -import { logger } from '@storybook/client-logger'; import { fetch } from 'global'; import semver from 'semver'; +import { logger } from '@storybook/client-logger'; -import { version as currentVersion } from '../../package.json'; +import { version as currentVersion } from '../version'; -const checkInterval = 24 * 60 * 60 * 1000; +import { Module, API } from '../index'; + +export interface Version { + version: string; + info?: string; + [key: string]: any; +} + +export interface SubState { + versions: { + [key: string]: { + [key: string]: any; + }; + latest?: Version; + next?: Version; + current?: Version; + }; + lastVersionCheck: number; + dismissedVersionNotification: undefined | string; +} +const checkInterval = 24 * 60 * 60 * 1000; const versionsUrl = 'https://storybook.js.org/versions.json'; -async function fetchLatestVersion() { - const fromFetch = await fetch(`${versionsUrl}?current=${currentVersion}`); + +async function fetchLatestVersion(v: string) { + const fromFetch = await fetch(`${versionsUrl}?current=${v}`); return fromFetch.json(); } -export default function({ store, mode }) { +export interface SubAPI { + getCurrentVersion: () => Version; + getLatestVersion: () => Version; + versionUpdateAvailable: () => boolean; +} + +export default function({ store, mode }: Module) { const { versions: persistedVersions = {}, - lastVersionCheck, + lastVersionCheck = 0, dismissedVersionNotification, } = store.getState(); @@ -35,7 +62,7 @@ export default function({ store, mode }) { dismissedVersionNotification, }; - const api = { + const api: SubAPI = { getCurrentVersion: () => { const { versions: { current }, @@ -55,21 +82,26 @@ export default function({ store, mode }) { const latest = api.getLatestVersion(); const current = api.getCurrentVersion(); + if (!latest || !latest.version) { + return true; + } return latest && semver.gt(latest.version, current.version); }, }; // Grab versions from the server/local storage right away - async function init({ api: { versionUpdateAvailable, getLatestVersion, addNotification } }) { + async function init({ api: fullApi }: API) { const { versions = {} } = store.getState(); const now = Date.now(); if (!lastVersionCheck || now - lastVersionCheck > checkInterval) { try { const { latest, next } = await fetchLatestVersion(currentVersion); - await store.setState( - { versions: { ...versions, latest, next }, lastVersionCheck: now }, + { + versions: { ...versions, latest, next }, + lastVersionCheck: now, + }, { persistence: 'permanent' } ); } catch (error) { @@ -77,8 +109,8 @@ export default function({ store, mode }) { } } - if (versionUpdateAvailable()) { - const latestVersion = getLatestVersion().version; + if (api.versionUpdateAvailable()) { + const latestVersion = api.getLatestVersion().version; if ( latestVersion !== dismissedVersionNotification && @@ -86,7 +118,7 @@ export default function({ store, mode }) { !semver.prerelease(latestVersion) && mode !== 'production' ) { - addNotification({ + fullApi.addNotification({ id: 'update', link: '/settings/about', content: `🎉 Storybook ${latestVersion} is available!`, diff --git a/lib/ui/src/core/store.js b/lib/api/src/store.ts similarity index 55% rename from lib/ui/src/core/store.js rename to lib/api/src/store.ts index 4bab42572701..628424dd4cc0 100644 --- a/lib/ui/src/core/store.js +++ b/lib/api/src/store.ts @@ -1,47 +1,87 @@ -// TODO -- make this TS? - import { localStorage, sessionStorage } from 'global'; import { parse, stringify } from 'telejson'; export const STORAGE_KEY = '@storybook/ui/store'; -function get(storage) { +import { State } from './index'; + +function get(storage: Storage) { const serialized = storage.getItem(STORAGE_KEY); return serialized ? parse(serialized) : {}; } -function set(storage, value) { +function set(storage: Storage, value: Patch) { storage.setItem(STORAGE_KEY, stringify(value, { maxDepth: 50 })); } -function update(storage, patch) { +function update(storage: Storage, patch: Patch) { const previous = get(storage); // Apply the same behaviour as react here set(storage, { ...previous, ...patch }); } +type GetState = () => State; +type SetState = (a: any, b: any) => any; + +interface Storage { + getItem(key: string): string | undefined; + setItem( + key: string, + value: string + ): + | { + [key: string]: any; + } + | undefined; +} + +interface Upstream { + getState: GetState; + setState: SetState; +} + +type Patch = Partial; + +type InputFnPatch = (s: State) => Patch; +type InputPatch = Patch | InputFnPatch; + +interface Options { + persistence: 'none' | 'session' | string; +} +type CallBack = (s: State) => void; +type CallbackOrOptions = CallBack | Options; + // Our store piggybacks off the internal React state of the Context Provider // It has been augmented to persist state to local/sessionStorage export default class Store { - constructor({ setState, getState }) { + upstreamGetState: GetState; + upstreamSetState: SetState; + + constructor({ setState, getState }: Upstream) { this.upstreamSetState = setState; this.upstreamGetState = getState; } // The assumption is that this will be called once, to initialize the React state // when the module is instanciated - getInitialState() { + getInitialState(base: State) { // We don't only merge at the very top level (the same way as React setState) // when you set keys, so it makes sense to do the same in combining the two storage modes // Really, you shouldn't store the same key in both places - return { ...get(localStorage), ...get(sessionStorage) }; + return { ...base, ...get(localStorage), ...get(sessionStorage) }; } getState() { return this.upstreamGetState(); } - async setState(inputPatch, cbOrOptions, inputOptions) { + async setState(inputPatch: InputPatch, options?: Options): Promise; + async setState(inputPatch: InputPatch, callback?: CallBack, options?: Options): Promise; + async setState( + inputPatch: InputPatch, + cbOrOptions?: CallbackOrOptions, + inputOptions?: Options + ): Promise { let callback; let options; if (typeof cbOrOptions === 'function') { @@ -52,13 +92,14 @@ export default class Store { } const { persistence = 'none' } = options || {}; - let patch; + let patch: Patch = {}; // What did the patch actually return - let delta; + let delta: Patch = {}; if (typeof inputPatch === 'function') { // Pass the same function, but just set delta on the way - patch = state => { - delta = inputPatch(state); + patch = (state: State) => { + const getDelta = inputPatch as InputFnPatch; + delta = getDelta(state); return delta; }; } else { @@ -66,7 +107,7 @@ export default class Store { delta = patch; } - const newState = await new Promise(resolve => { + const newState: State = await new Promise(resolve => { this.upstreamSetState(patch, resolve); }); diff --git a/lib/ui/src/core/notifications.test.js b/lib/api/src/tests/notifications.test.js similarity index 93% rename from lib/ui/src/core/notifications.test.js rename to lib/api/src/tests/notifications.test.js index af58a0503f81..c9e68f83e895 100644 --- a/lib/ui/src/core/notifications.test.js +++ b/lib/api/src/tests/notifications.test.js @@ -1,4 +1,4 @@ -import initNotifications from './notifications'; +import initNotifications from '../modules/notifications'; describe('notifications API', () => { it('allows adding notifications', () => { diff --git a/lib/ui/src/libs/shortcut.test.ts b/lib/api/src/tests/shortcut.test.js similarity index 94% rename from lib/ui/src/libs/shortcut.test.ts rename to lib/api/src/tests/shortcut.test.js index 5857517bde45..5ddc67047177 100644 --- a/lib/ui/src/libs/shortcut.test.ts +++ b/lib/api/src/tests/shortcut.test.js @@ -1,5 +1,7 @@ -import { eventToShortcut, keyToSymbol } from './shortcut'; -const ev = (attr: object) => new KeyboardEvent('keydown', { ...attr }); +import { KeyboardEvent } from 'global'; +import { eventToShortcut, keyToSymbol } from '../lib/shortcut'; + +const ev = attr => new KeyboardEvent('keydown', { ...attr }); describe('eventToShortcut', () => { test('it handles alt key inputs', () => { diff --git a/lib/ui/src/core/shortcuts.test.js b/lib/api/src/tests/shortcuts.test.js similarity index 97% rename from lib/ui/src/core/shortcuts.test.js rename to lib/api/src/tests/shortcuts.test.js index 3a78318340ac..a9a3d149866a 100644 --- a/lib/ui/src/core/shortcuts.test.js +++ b/lib/api/src/tests/shortcuts.test.js @@ -1,4 +1,4 @@ -import initShortcuts from './shortcuts'; +import initShortcuts from '../modules/shortcuts'; function createMockStore() { let state = {}; diff --git a/lib/ui/src/core/store.test.js b/lib/api/src/tests/store.test.js similarity index 99% rename from lib/ui/src/core/store.test.js rename to lib/api/src/tests/store.test.js index d3c9774fb509..9849bdbae693 100644 --- a/lib/ui/src/core/store.test.js +++ b/lib/api/src/tests/store.test.js @@ -1,7 +1,7 @@ import { localStorage, sessionStorage } from 'global'; import flushPromises from 'flush-promises'; -import Store, { STORAGE_KEY } from './store'; +import Store, { STORAGE_KEY } from '../store'; jest.mock('global', () => ({ sessionStorage: { diff --git a/lib/ui/src/core/stories.test.js b/lib/api/src/tests/stories.test.js similarity index 99% rename from lib/ui/src/core/stories.test.js rename to lib/api/src/tests/stories.test.js index b200ed0b8b9a..47bbf50f4fae 100644 --- a/lib/ui/src/core/stories.test.js +++ b/lib/api/src/tests/stories.test.js @@ -1,4 +1,4 @@ -import initStories from './stories'; +import initStories from '../modules/stories'; function createMockStore() { let state = {}; @@ -18,6 +18,7 @@ describe('stories API', () => { }); expect(state).toEqual({ + storiesConfigured: false, storiesHash: {}, storyId: 'id', viewMode: 'story', diff --git a/lib/ui/src/core/url.test.js b/lib/api/src/tests/url.test.js similarity index 99% rename from lib/ui/src/core/url.test.js rename to lib/api/src/tests/url.test.js index f8b06e28e865..3c2845beed54 100644 --- a/lib/ui/src/core/url.test.js +++ b/lib/api/src/tests/url.test.js @@ -1,6 +1,6 @@ import qs from 'qs'; -import initURL from './url'; +import initURL from '../modules/url'; jest.useFakeTimers(); diff --git a/lib/ui/src/core/versions.test.js b/lib/api/src/tests/versions.test.js similarity index 94% rename from lib/ui/src/core/versions.test.js rename to lib/api/src/tests/versions.test.js index 1dc54fca81f2..c13af6539d9b 100644 --- a/lib/ui/src/core/versions.test.js +++ b/lib/api/src/tests/versions.test.js @@ -1,7 +1,7 @@ import { fetch } from 'global'; -import initVersions from './versions'; +import initVersions from '../modules/versions'; -jest.mock('../../package.json', () => ({ +jest.mock('../version', () => ({ version: '3.0.0', })); @@ -9,8 +9,15 @@ jest.mock('global', () => ({ fetch: jest.fn(), })); +jest.mock('@storybook/client-logger'); + function createMockStore() { - let state = {}; + let state = { + versions: { + latest: {}, + current: {}, + }, + }; return { getState: jest.fn().mockImplementation(() => state), setState: jest.fn().mockImplementation(s => { @@ -26,11 +33,13 @@ const makeResponse = (latest, next) => { }, }; return { - json: jest.fn().mockResolvedValue({ - latest: { - version: latest, - }, - ...nextVersion, + json: jest.fn(() => { + return Promise.resolve({ + latest: { + version: latest, + }, + ...nextVersion, + }); }), }; }; @@ -47,9 +56,7 @@ describe('versions API', () => { const store = createMockStore(); const { state } = initVersions({ store }); - expect(state.versions).toEqual({ - current: { version: '3.0.0' }, - }); + expect(state.versions.current).toEqual({ version: '3.0.0' }); }); it('sets initial state based on persisted versions', async () => { @@ -72,10 +79,14 @@ describe('versions API', () => { const store = createMockStore(); const { state: initialState, init, api } = initVersions({ store }); store.setState(initialState); + store.setState.mockReset(); fetch.mockResolvedValueOnce(newResponse); - store.setState.mockReset(); + await init({ api: { addNotification: jest.fn(), ...api } }); + + // expect(fetch.mock.calls).toBe(1); + expect(store.setState).toHaveBeenCalledWith( { versions: { @@ -95,6 +106,7 @@ describe('versions API', () => { current: { version: '3.0.0' }, latest: { version: '3.1.0' }, }, + lastVersionCheck: 0, }); const { state: initialState, init, api } = initVersions({ store }); @@ -106,8 +118,8 @@ describe('versions API', () => { expect(store.setState).toHaveBeenCalledWith( { versions: { - latest: { version: '4.0.0' }, current: { version: '3.0.0' }, + latest: { version: '4.0.0' }, }, lastVersionCheck: expect.any(Number), }, @@ -134,9 +146,7 @@ describe('versions API', () => { fetch.mockRejectedValueOnce(new Error('fetch failed')); await init({ api: { addNotification: jest.fn(), ...api } }); - expect(store.getState().versions).toEqual({ - current: { version: '3.0.0' }, - }); + expect(store.getState().versions.current).toEqual({ version: '3.0.0' }); }); describe('notifications', () => { diff --git a/lib/api/src/typings.d.ts b/lib/api/src/typings.d.ts new file mode 100644 index 000000000000..dffcbc4aa830 --- /dev/null +++ b/lib/api/src/typings.d.ts @@ -0,0 +1,2 @@ +declare module 'global'; +declare module 'telejson'; diff --git a/lib/api/src/version.ts b/lib/api/src/version.ts new file mode 100644 index 000000000000..8ffe134de7a4 --- /dev/null +++ b/lib/api/src/version.ts @@ -0,0 +1 @@ +export const version = '5.1.0-alpha.9'; diff --git a/lib/api/tsconfig.json b/lib/api/tsconfig.json new file mode 100644 index 000000000000..4894223ce827 --- /dev/null +++ b/lib/api/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/lib/client-api/src/client_api.js b/lib/client-api/src/client_api.js index 0a3570206217..8abc6536e28a 100644 --- a/lib/client-api/src/client_api.js +++ b/lib/client-api/src/client_api.js @@ -163,7 +163,7 @@ export default class ClientApi { }); } - const fileName = m && typeof m.id === 'string' ? m.id : undefined; + const fileName = m && m.id ? `${m.id}` : undefined; const { hierarchyRootSeparator, hierarchySeparator } = this.getSeparators(); const baseOptions = { diff --git a/lib/client-api/src/client_api.test.js b/lib/client-api/src/client_api.test.js index 257401a6e1ee..fa86493ef5a9 100644 --- a/lib/client-api/src/client_api.test.js +++ b/lib/client-api/src/client_api.test.js @@ -271,7 +271,7 @@ describe('preview.client_api', () => { ]); }); - it('should ignore non-string ids from module', () => { + it('should stringify ids from module', () => { const { clientApi: { getStorybook, storiesOf }, } = getContext(); @@ -284,6 +284,7 @@ describe('preview.client_api', () => { expect(storybook).toEqual([ { kind: 'kind', + fileName: '1211', stories: [ { name: 'name', @@ -297,6 +298,8 @@ describe('preview.client_api', () => { describe('hot module loading', () => { class MockModule { + id = 'mock-module-id'; + hot = { callbacks: [], dispose(fn) { @@ -339,6 +342,7 @@ describe('preview.client_api', () => { const firstStorybook = getStorybook(); expect(firstStorybook).toEqual([ { + fileName: expect.any(String), kind: 'kind', stories: [{ name: 'story', render: expect.anything() }], }, @@ -355,6 +359,7 @@ describe('preview.client_api', () => { const secondStorybook = getStorybook(); expect(secondStorybook).toEqual([ { + fileName: expect.any(String), kind: 'kind', stories: [{ name: 'story', render: expect.anything() }], }, diff --git a/lib/client-api/src/story_store.test.js b/lib/client-api/src/story_store.test.js index 095c79ef4403..b68499539f24 100644 --- a/lib/client-api/src/story_store.test.js +++ b/lib/client-api/src/story_store.test.js @@ -20,6 +20,14 @@ jest.mock('global', () => ({ }, })); +jest.mock('@storybook/node-logger', () => ({ + logger: { + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, +})); + const channel = createChannel({ page: 'preview' }); const make = (kind, name, storyFn, parameters = {}) => [ diff --git a/lib/core/src/client/manager/provider.js b/lib/core/src/client/manager/provider.js index 369a08934025..8ebdf8dee184 100644 --- a/lib/core/src/client/manager/provider.js +++ b/lib/core/src/client/manager/provider.js @@ -6,17 +6,21 @@ import Events from '@storybook/core-events'; export default class ReactProvider extends Provider { constructor() { super(); - this.channel = createChannel({ page: 'manager' }); - addons.setChannel(this.channel); - this.channel.emit(Events.CHANNEL_CREATED); + const channel = createChannel({ page: 'manager' }); + + addons.setChannel(channel); + channel.emit(Events.CHANNEL_CREATED); + + this.addons = addons; + this.channel = channel; } getElements(type) { - return addons.getElements(type); + return this.addons.getElements(type); } handleAPI(api) { - addons.loadAddons(api); + this.addons.loadAddons(api); } } diff --git a/lib/core/src/server/presets.test.js b/lib/core/src/server/presets.test.js index 1e48f7e7a0c6..5cb355a54630 100644 --- a/lib/core/src/server/presets.test.js +++ b/lib/core/src/server/presets.test.js @@ -9,6 +9,14 @@ function mockPreset(name, mockPresetObject) { jest.mock(name, () => mockPresetObject, { virtual: true }); } +jest.mock('@storybook/node-logger', () => ({ + logger: { + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, +})); + describe('presets', () => { it('does not throw when there is no preset file', async () => { const loadPresets = require.requireActual('./presets').default; diff --git a/lib/router/package.json b/lib/router/package.json index c20e02ab60c4..ad2558e02695 100644 --- a/lib/router/package.json +++ b/lib/router/package.json @@ -16,6 +16,7 @@ }, "license": "MIT", "main": "dist/index.js", + "types": "dist/index.d.ts", "scripts": { "prepare": "node ../../scripts/prepare.js" }, @@ -33,5 +34,8 @@ }, "publishConfig": { "access": "public" + }, + "devDependencies": { + "@types/reach__router": "^1.2.3" } } diff --git a/lib/router/src/router.tsx b/lib/router/src/router.tsx index bb3d87d6e8e5..7a1bdcdb0187 100644 --- a/lib/router/src/router.tsx +++ b/lib/router/src/router.tsx @@ -1,37 +1,17 @@ import { document } from 'global'; import React from 'react'; -import { Link, Location, navigate, LocationProvider } from '@reach/router'; +import { Link, Location, navigate, LocationProvider, RouteComponentProps } from '@reach/router'; import { ToggleVisibility } from './visibility'; import { queryFromString, storyDataFromString, getMatch } from './utils'; -interface Location { - search: string; - href: string; - origin: string; - protocol: 'http:' | 'https:'; - host: string; - hostname: string; - port: string; - pathname: string; - hash: string; - state: { - key: string; - }; - key: string; - reload: () => void; - replace: (url: string) => void; - assign: (url: string) => void; - toString: () => string; -} - -interface RenderData { - path: string; - location: Location; - navigate: (to: string) => void; +interface Other { viewMode?: string; storyId?: string; } + +export type RenderData = RouteComponentProps & Other; + interface MatchingData { match: null | { path: string }; } @@ -74,7 +54,7 @@ QueryLink.displayName = 'QueryLink'; // and will be called whenever it changes when it changes const QueryLocation = ({ children }: QueryLocationProps) => ( - {({ location }: { location: Location }) => { + {({ location }: RouteComponentProps): React.ReactNode => { const { path } = queryFromString(location.search); const { viewMode, storyId } = storyDataFromString(path); return children({ path, location, navigate: queryNavigate, viewMode, storyId }); diff --git a/lib/router/src/typings.d.ts b/lib/router/src/typings.d.ts index e78abbafc83e..75b3c91658af 100644 --- a/lib/router/src/typings.d.ts +++ b/lib/router/src/typings.d.ts @@ -1,4 +1,3 @@ // todo the following packages need definition files or a TS migration -declare module 'qs'; declare module 'global'; -declare module '@reach/router'; +declare module 'qs'; diff --git a/lib/router/src/utils.ts b/lib/router/src/utils.ts index 9a126db2fc26..5477569a051e 100644 --- a/lib/router/src/utils.ts +++ b/lib/router/src/utils.ts @@ -30,7 +30,7 @@ const sanitizeSafe = (string: string, part: string) => { export const toId = (kind: string, name: string) => `${sanitizeSafe(kind, 'kind')}--${sanitizeSafe(name, 'name')}`; -export const storyDataFromString: (path: string) => StoryData = memoize(1000)( +export const storyDataFromString: (path?: string) => StoryData = memoize(1000)( (path: string | undefined | null) => { const result: StoryData = { viewMode: undefined, @@ -50,9 +50,15 @@ export const storyDataFromString: (path: string) => StoryData = memoize(1000)( } ); -export const queryFromString = memoize(1000)(s => qs.parse(s, { ignoreQueryPrefix: true })); +interface Query { + [key: string]: any; +} + +export const queryFromString = memoize(1000)( + (s: string): Query => qs.parse(s, { ignoreQueryPrefix: true }) +); export const queryFromLocation = (location: { search: string }) => queryFromString(location.search); -export const stringifyQuery = (query: object) => +export const stringifyQuery = (query: Query) => qs.stringify(query, { addQueryPrefix: true, encode: false }); export const getMatch = memoize(1000)( diff --git a/lib/router/tsconfig.json b/lib/router/tsconfig.json index 8876bb6737a1..c477a63e62bf 100644 --- a/lib/router/tsconfig.json +++ b/lib/router/tsconfig.json @@ -8,6 +8,8 @@ "src/**/*" ], "exclude": [ + "dist", + "src/tests/**/*", "src/__tests__/**/*" ] } diff --git a/lib/theming/src/typings.d.ts b/lib/theming/src/typings.d.ts index 736edc60c4bc..a767a7664a30 100644 --- a/lib/theming/src/typings.d.ts +++ b/lib/theming/src/typings.d.ts @@ -1,4 +1,4 @@ // todo the following packages need definition files or a TS migration -declare module 'react-inspector'; declare module 'lodash.mergewith'; declare module 'lodash.isequal'; +declare module 'react-inspector'; diff --git a/lib/ui/package.json b/lib/ui/package.json index d351b8c20455..93d705434810 100644 --- a/lib/ui/package.json +++ b/lib/ui/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@storybook/addons": "5.1.0-alpha.9", + "@storybook/api": "5.1.0-alpha.9", "@storybook/client-logger": "5.1.0-alpha.9", "@storybook/components": "5.1.0-alpha.9", "@storybook/core-events": "5.1.0-alpha.9", diff --git a/lib/ui/src/containers/nav.js b/lib/ui/src/containers/nav.js index 9d6072f2b241..511ef838fd75 100755 --- a/lib/ui/src/containers/nav.js +++ b/lib/ui/src/containers/nav.js @@ -2,13 +2,18 @@ import React from 'react'; import memoize from 'memoizerific'; import { Badge } from '@storybook/components'; -import ListItemIcon from '../components/sidebar/ListItemIcon'; +import { Consumer } from '@storybook/api'; import { shortcutToHumanString } from '../libs/shortcut'; -import { focusableUIElements } from '../core/layout'; +import ListItemIcon from '../components/sidebar/ListItemIcon'; import Sidebar from '../components/sidebar/Sidebar'; -import { Consumer } from '../core/context'; + +const focusableUIElements = { + storySearchField: 'storybook-explorer-searchfield', + storyListMenu: 'storybook-explorer-menu', + storyPanelRoot: 'storybook-panel-root', +}; const createMenu = memoize(1)((api, shortcutKeys, isFullscreen, showPanel, showNav) => [ { @@ -90,7 +95,7 @@ const createMenu = memoize(1)((api, shortcutKeys, isFullscreen, showPanel, showN }, ]); -export const mapper = (state, api) => { +export const mapper = ({ state, api }) => { const { ui: { name, url }, viewMode, @@ -114,5 +119,5 @@ export const mapper = (state, api) => { }; export default props => ( - {({ state, api }) => } + {fromState => } ); diff --git a/lib/ui/src/containers/notifications.js b/lib/ui/src/containers/notifications.js index 641709e3b54a..afaef819751b 100644 --- a/lib/ui/src/containers/notifications.js +++ b/lib/ui/src/containers/notifications.js @@ -1,9 +1,10 @@ import React from 'react'; +import { Consumer } from '@storybook/api'; + import Notifications from '../components/notifications/notifications'; -import { Consumer } from '../core/context'; -export const mapper = state => { +export const mapper = ({ state }) => { const { notifications } = state; return { @@ -12,7 +13,7 @@ export const mapper = state => { }; const NotificationConnect = props => ( - {({ state, api }) => } + {fromState => } ); export default NotificationConnect; diff --git a/lib/ui/src/containers/panel.js b/lib/ui/src/containers/panel.js index ef11db10c797..de4d62f233f2 100644 --- a/lib/ui/src/containers/panel.js +++ b/lib/ui/src/containers/panel.js @@ -1,8 +1,8 @@ import React from 'react'; import memoize from 'memoizerific'; +import { Consumer } from '@storybook/api'; import AddonPanel from '../components/panel/panel'; -import { Consumer } from '../core/context'; const createPanelActions = memoize(1)(api => ({ onSelect: panel => api.setSelectedPanel(panel), @@ -10,17 +10,13 @@ const createPanelActions = memoize(1)(api => ({ togglePosition: () => api.togglePanelPosition(), })); -export default props => ( - - {({ state, api }) => { - const customProps = { - panels: api.getPanels(), - selectedPanel: api.getSelectedPanel(), - panelPosition: state.layout.panelPosition, - actions: createPanelActions(api), - }; +const mapper = ({ state, api }) => ({ + panels: api.getPanels(), + selectedPanel: api.getSelectedPanel(), + panelPosition: state.layout.panelPosition, + actions: createPanelActions(api), +}); - return ; - }} - +export default props => ( + {customProps => } ); diff --git a/lib/ui/src/containers/preview.js b/lib/ui/src/containers/preview.js index 5823a006a127..d20a4574bb9d 100644 --- a/lib/ui/src/containers/preview.js +++ b/lib/ui/src/containers/preview.js @@ -1,7 +1,8 @@ import React from 'react'; import memoize from 'memoizerific'; -import { Consumer } from '../core/context'; +import { Consumer } from '@storybook/api'; + import { Preview } from '../components/preview/preview'; const nonAlphanumSpace = /[^a-z0-9 ]/gi; @@ -13,37 +14,25 @@ const addExtraWhiteSpace = input => const createPreviewActions = memoize(1)(api => ({ toggleFullscreen: () => api.toggleFullscreen(), })); -const createProps = (api, layout, location, path, storyId, viewMode, selected) => ({ - api, - getElements: api.getElements, - actions: createPreviewActions(api), - options: layout, - location, - path, - storyId, - viewMode, - description: selected ? addExtraWhiteSpace(`${selected.kind} - ${selected.name}`) : '', -}); +const mapper = ({ api, state: { layout, location, path, storyId, viewMode, selected } }) => + api.renderPreview + ? { renderPreview: api.renderPreview } + : { + api, + getElements: api.getElements, + actions: createPreviewActions(api), + options: layout, + location, + path, + storyId, + viewMode, + description: selected ? addExtraWhiteSpace(`${selected.kind} - ${selected.name}`) : '', + }; const PreviewConnected = React.memo(props => ( - - {({ state, api }) => - api.renderPreview ? ( - api.renderPreview(state, api) - ) : ( - - ) + + {fromState => + fromState.renderPreview ? fromState.renderPreview() : } )); diff --git a/lib/ui/src/core/addons.js b/lib/ui/src/core/addons.js deleted file mode 100644 index 7ebc7cc5ac4a..000000000000 --- a/lib/ui/src/core/addons.js +++ /dev/null @@ -1,35 +0,0 @@ -import { types } from '@storybook/addons'; - -export function ensurePanel(panels, selectedPanel, currentPanel) { - const keys = Object.keys(panels); - - if (keys.indexOf(selectedPanel) >= 0) { - return selectedPanel; - } - - if (keys.length) { - return keys[0]; - } - return currentPanel; -} - -export default ({ provider, store }) => { - const api = { - getElements: type => provider.getElements(type), - getPanels: () => api.getElements(types.PANEL), - getSelectedPanel: () => { - const { selectedPanel } = store.getState(); - return ensurePanel(api.getPanels(), selectedPanel, selectedPanel); - }, - setSelectedPanel: panelName => { - store.setState({ selectedPanel: panelName }, { persistence: 'session' }); - }, - }; - - return { - api, - state: { - selectedPanel: ensurePanel(api.getPanels(), store.getState().selectedPanel), - }, - }; -}; diff --git a/lib/ui/src/core/channel.js b/lib/ui/src/core/channel.js deleted file mode 100644 index bdaec35fa95c..000000000000 --- a/lib/ui/src/core/channel.js +++ /dev/null @@ -1,28 +0,0 @@ -import deprecate from 'util-deprecate'; -import { STORY_CHANGED } from '@storybook/core-events'; - -export default ({ provider }) => { - const api = { - getChannel: () => provider.channel, - on: (type, cb, peer = true) => { - if (peer) { - provider.channel.addPeerListener(type, cb); - } else { - provider.channel.addListener(type, cb); - } - - return () => provider.channel.removeListener(type, cb); - }, - off: (type, cb) => { - provider.channel.removeListener(type, cb); - }, - emit: (type, event) => { - provider.channel.emit(type, event); - }, - onStory: deprecate( - cb => api.on(STORY_CHANGED, cb), - 'onStory(...) has been replaced with on(STORY_CHANGED, ...)' - ), - }; - return { api }; -}; diff --git a/lib/ui/src/core/context.js b/lib/ui/src/core/context.js deleted file mode 100644 index 0228bea413b5..000000000000 --- a/lib/ui/src/core/context.js +++ /dev/null @@ -1,163 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - -import Events, { STORIES_CONFIGURED } from '@storybook/core-events'; - -import initProviderApi from './init-provider-api'; - -import Store from './store'; -import getInitialState from './initial-state'; - -import initAddons from './addons'; -import initChannel from './channel'; -import initNotifications from './notifications'; -import initStories from './stories'; -import initLayout from './layout'; -import initShortcuts from './shortcuts'; -import initURL from './url'; -import initVersions from './versions'; - -const Context = React.createContext({ api: undefined, state: getInitialState({}) }); - -const { STORY_CHANGED, SET_STORIES, SELECT_STORY } = Events; - -export class Provider extends Component { - static propTypes = { - navigate: PropTypes.func.isRequired, - provider: PropTypes.shape({}).isRequired, - location: PropTypes.shape({}).isRequired, - path: PropTypes.string, - viewMode: PropTypes.oneOf(['story', 'info']), - storyId: PropTypes.string, - children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired, - }; - - static defaultProps = { - viewMode: undefined, - storyId: undefined, - path: undefined, - }; - - constructor(props) { - super(props); - const { provider, location, path, viewMode, storyId, navigate } = props; - - const store = new Store({ - getState: () => this.state, - setState: (a, b) => this.setState(a, b), - }); - - // Initialize the state to be the initial (persisted) state of the store. - // This gives the modules the chance to read the persisted state, apply their defaults - // and override if necessary - - const apiData = { - navigate, - store, - provider, - location, - path, - viewMode, - storyId, - mode: process.env.NODE_ENV, - }; - - this.state = store.getInitialState(); - - this.modules = [ - initChannel, - initAddons, - initLayout, - initNotifications, - initShortcuts, - initStories, - initURL, - initVersions, - ].map(initModule => initModule(apiData)); - - // Create our initial state by combining the initial state of all modules, then overlaying any saved state - const state = getInitialState(...this.modules.map(m => m.state)); - - // Get our API by combining the APIs exported by each module - const combo = Object.assign({ navigate }, ...this.modules.map(m => m.api)); - - const api = initProviderApi({ provider, store, api: combo }); - - api.on(STORY_CHANGED, id => { - const options = api.getParameters(id, 'options'); - - api.setOptions(options); - }); - - api.on(SET_STORIES, data => { - api.setStories(data.stories); - - const options = storyId - ? api.getParameters(storyId, 'options') - : api.getParameters(Object.keys(data.stories)[0], 'options'); - - api.setOptions(options); - }); - api.on(STORIES_CONFIGURED, () => { - store.setState({ storiesConfigured: true }); - }); - api.on(SELECT_STORY, ({ kind, story, ...rest }) => { - api.selectStory(kind, story, rest); - }); - - this.state = state; - this.api = api; - } - - componentDidMount() { - // Now every module has had a chance to set its API, call init on each module which gives it - // a chance to do things that call other modules' APIs. - this.modules.forEach(({ init }) => { - if (init) { - init({ api: this.api }); - } - }); - } - - shouldComponentUpdate(nextProps, nextState) { - const { state: prevState, props: prevProps } = this; - - if (prevState !== nextState) { - return true; - } - if (prevProps.path !== nextProps.path) { - return true; - } - return false; - } - - static getDerivedStateFromProps = (props, state) => { - if (state.path !== props.path) { - return { - ...state, - location: props.location, - path: props.path, - viewMode: props.viewMode, - storyId: props.storyId, - }; - } - return null; - }; - - render() { - const { children } = this.props; - const value = { - state: this.state, - api: this.api, - }; - - return ( - - {typeof children === 'function' ? children(value) : children} - - ); - } -} -Provider.displayName = 'Manager'; - -export const { Consumer } = Context; diff --git a/lib/ui/src/core/init-provider-api.js b/lib/ui/src/core/init-provider-api.js deleted file mode 100644 index 10f03e8a47a0..000000000000 --- a/lib/ui/src/core/init-provider-api.js +++ /dev/null @@ -1,13 +0,0 @@ -export default ({ provider, api }) => { - const providerAPI = { - ...api, - }; - - provider.handleAPI(providerAPI); - - if (provider.renderPreview) { - providerAPI.renderPreview = provider.renderPreview; - } - - return providerAPI; -}; diff --git a/lib/ui/src/core/initial-state.js b/lib/ui/src/core/initial-state.js deleted file mode 100644 index 5eee085d6286..000000000000 --- a/lib/ui/src/core/initial-state.js +++ /dev/null @@ -1,9 +0,0 @@ -import merge from '../libs/merge'; - -const initial = { - customQueryParams: {}, - storiesConfigured: false, -}; - -// Returns the initialState of the app -export default (...additions) => additions.reduce((acc, item) => merge(acc, item), initial); diff --git a/lib/ui/src/core/notifications.js b/lib/ui/src/core/notifications.js deleted file mode 100644 index da4435d7a5d5..000000000000 --- a/lib/ui/src/core/notifications.js +++ /dev/null @@ -1,27 +0,0 @@ -export default function({ store }) { - const api = { - addNotification: ({ id, ...notification }) => { - // Get rid of it if already exists - api.clearNotification(id); - - const { notifications } = store.getState(); - - store.setState({ notifications: [...notifications, { id, ...notification }] }); - }, - - clearNotification: id => { - const { notifications } = store.getState(); - - store.setState({ notifications: notifications.filter(n => n.id !== id) }); - - const notification = notifications.find(n => n.id === id); - if (notification && notification.onClear) { - notification.onClear(); - } - }, - }; - - const state = { notifications: [] }; - - return { api, state }; -} diff --git a/lib/ui/src/index.js b/lib/ui/src/index.js index 2e62922d4aaf..2a8ee365426f 100644 --- a/lib/ui/src/index.js +++ b/lib/ui/src/index.js @@ -3,9 +3,9 @@ import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; import { Location, LocationProvider } from '@storybook/router'; +import { Provider as ManagerProvider } from '@storybook/api'; import { ThemeProvider, ensure as ensureTheme } from '@storybook/theming'; import { HelmetProvider } from 'react-helmet-async'; -import { Provider as ManagerProvider } from './core/context'; import App from './app'; diff --git a/lib/ui/src/libs/merge.js b/lib/ui/src/libs/merge.js index d8760b6bf5ef..ea6e95ca8c19 100644 --- a/lib/ui/src/libs/merge.js +++ b/lib/ui/src/libs/merge.js @@ -16,7 +16,7 @@ export default (a, b) => return objValue; } if (Array.isArray(objValue)) { - logger.log('the types mismatch, picking', objValue); + logger.log(['the types mismatch, picking', objValue]); return objValue; } return undefined; diff --git a/lib/ui/src/libs/shortcut.js b/lib/ui/src/libs/shortcut.js new file mode 100644 index 000000000000..7cd4e644533a --- /dev/null +++ b/lib/ui/src/libs/shortcut.js @@ -0,0 +1,107 @@ +import { navigator } from 'global'; + +// The shortcut is our JSON-ifiable representation of a shortcut combination + +export const isMacLike = () => + navigator && navigator.platform ? !!navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i) : false; +export const controlOrMetaSymbol = () => (isMacLike() ? '⌘' : 'ctrl'); +export const controlOrMetaKey = () => (isMacLike() ? 'meta' : 'control'); +export const optionOrAltSymbol = () => (isMacLike() ? '⌥' : 'alt'); + +export const isShortcutTaken = (arr1, arr2) => JSON.stringify(arr1) === JSON.stringify(arr2); + +// Map a keyboard event to a keyboard shortcut +// NOTE: if we change the fields on the event that we need, we'll need to update the serialization in core/preview/start.js +export const eventToShortcut = e => { + // Meta key only doesn't map to a shortcut + if (['Meta', 'Alt', 'Control', 'Shift'].includes(e.key)) { + return null; + } + + const keys = []; + if (e.altKey) { + keys.push('alt'); + } + if (e.ctrlKey) { + keys.push('control'); + } + if (e.metaKey) { + keys.push('meta'); + } + if (e.shiftKey) { + keys.push('shift'); + } + + if (e.key && e.key.length === 1 && e.key !== ' ') { + keys.push(e.key.toUpperCase()); + } + if (e.key === ' ') { + keys.push('space'); + } + if (e.key === 'Escape') { + keys.push('escape'); + } + if (e.key === 'ArrowRight') { + keys.push('ArrowRight'); + } + if (e.key === 'ArrowDown') { + keys.push('ArrowDown'); + } + if (e.key === 'ArrowUp') { + keys.push('ArrowUp'); + } + if (e.key === 'ArrowLeft') { + keys.push('ArrowLeft'); + } + + return keys.length > 0 ? keys : null; +}; + +export const shortcutMatchesShortcut = (inputShortcut, shortcut) => + inputShortcut && + inputShortcut.length === shortcut.length && + !inputShortcut.find((key, i) => key !== shortcut[i]); + +// Should this keyboard event trigger this keyboard shortcut? +export const eventMatchesShortcut = (e, shortcut) => + shortcutMatchesShortcut(eventToShortcut(e), shortcut); + +export const keyToSymbol = key => { + if (key === 'alt') { + return optionOrAltSymbol(); + } + if (key === 'control') { + return '⌃'; + } + if (key === 'meta') { + return '⌘'; + } + if (key === 'shift') { + return '⇧​'; + } + if (key === 'Enter' || key === 'Backspace' || key === 'Esc') { + return ''; + } + if (key === 'escape') { + return ''; + } + if (key === ' ') { + return 'SPACE'; + } + if (key === 'ArrowUp') { + return '↑'; + } + if (key === 'ArrowDown') { + return '↓'; + } + if (key === 'ArrowLeft') { + return '←'; + } + if (key === 'ArrowRight') { + return '→'; + } + return key.toUpperCase(); +}; + +// Display the shortcut as a human readable string +export const shortcutToHumanString = shortcut => shortcut.map(keyToSymbol).join(' '); diff --git a/lib/ui/src/provider.js b/lib/ui/src/provider.js index 8b0abf77a1bc..6114e44d0d09 100644 --- a/lib/ui/src/provider.js +++ b/lib/ui/src/provider.js @@ -1,4 +1,8 @@ export default class Provider { + getElements() { + throw new Error('Provider.getElements() is not implemented!'); + } + handleAPI() { throw new Error('Provider.handleAPI() is not implemented!'); } diff --git a/lib/ui/src/settings/about_page.js b/lib/ui/src/settings/about_page.js index cd86c0bcef2c..3478df85e886 100644 --- a/lib/ui/src/settings/about_page.js +++ b/lib/ui/src/settings/about_page.js @@ -1,9 +1,10 @@ +import { history } from 'global'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; + import { Route } from '@storybook/router'; -import { history } from 'global'; +import { Consumer } from '@storybook/api'; -import { Consumer } from '../core/context'; import AboutScreen from './about'; // Clear a notification on mount. This could be exported by core/notifications.js perhaps? diff --git a/lib/ui/src/settings/shortcuts.stories.js b/lib/ui/src/settings/shortcuts.stories.js index 2c7953a057a3..ab9cdf5427ac 100644 --- a/lib/ui/src/settings/shortcuts.stories.js +++ b/lib/ui/src/settings/shortcuts.stories.js @@ -2,7 +2,25 @@ import React from 'react'; import { actions as makeActions } from '@storybook/addon-actions'; import ShortcutsScreen from './shortcuts'; -import { defaultShortcuts } from '../core/shortcuts'; + +const defaultShortcuts = { + fullScreen: ['F'], + togglePanel: ['A'], + panelPosition: ['D'], + toggleNav: ['S'], + toolbar: ['T'], + search: ['/'], + focusNav: ['1'], + focusIframe: ['2'], + focusPanel: ['3'], + prevComponent: ['alt', 'ArrowUp'], + nextComponent: ['alt', 'ArrowDown'], + prevStory: ['alt', 'ArrowLeft'], + nextStory: ['alt', 'ArrowRight'], + shortcutsPage: ['ctrl', 'shift', ','], + aboutPage: [','], + escape: ['escape'], // This one is not customizable +}; const actions = makeActions( 'setShortcut', diff --git a/lib/ui/src/settings/shortcuts_page.js b/lib/ui/src/settings/shortcuts_page.js index 27ab109223a6..ac5a5019e958 100644 --- a/lib/ui/src/settings/shortcuts_page.js +++ b/lib/ui/src/settings/shortcuts_page.js @@ -1,16 +1,17 @@ +import { history } from 'global'; import React from 'react'; + import { Route } from '@storybook/router'; -import { history } from 'global'; +import { Consumer } from '@storybook/api'; -import { Consumer } from '../core/context'; import ShortcutsScreen from './shortcuts'; +const mapper = ({ api }) => api; + export default () => ( - - {({ - api: { getShortcutKeys, setShortcut, restoreDefaultShortcut, restoreAllDefaultShortcuts }, - }) => ( + + {({ getShortcutKeys, setShortcut, restoreDefaultShortcut, restoreAllDefaultShortcuts }) => ( fn => fn); + // mock console.info calls for cleaner test execution global.console.info = jest.fn().mockImplementation(() => {}); +global.console.debug = jest.fn().mockImplementation(() => {}); // mock local storage calls const localStorageMock = { diff --git a/scripts/test.js b/scripts/test.js index fb65a6ed21ca..88a4051a06f3 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -81,6 +81,18 @@ const tasks = { option: '--runInBand', extraParam: '--runInBand', }), + w2: createOption({ + name: `Run all tests in max 2 processes process ${chalk.gray('(w2)')}`, + defaultValue: false, + option: '--w2', + extraParam: '-w 2', + }), + reportLeaks: createOption({ + name: `report memory leaks ${chalk.gray('(reportLeaks)')}`, + defaultValue: false, + option: '--reportLeaks', + extraParam: '--detectLeaks', + }), update: createOption({ name: `Update all snapshots ${chalk.gray('(update)')}`, defaultValue: false, @@ -161,13 +173,14 @@ selection const jestProjects = projects.filter(key => key.isJest).map(key => key.projectLocation); const nonJestProjects = projects.filter(key => !key.isJest); const extraParams = getExtraParams(list).join(' '); + const jest = path.join(__dirname, '..', 'node_modules', '.bin', 'jest'); if (jestProjects.length > 0) { const projectsParam = jestProjects.some(project => project === '') ? '' : `--projects ${jestProjects.join(' ')}`; - spawn(`jest ${projectsParam} ${extraParams}`); + spawn(`node --max_old_space_size=4096 ${jest} ${projectsParam} ${extraParams}`); } nonJestProjects.forEach(key => diff --git a/yarn.lock b/yarn.lock index d3ae1f0de0e7..a5578d95888f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -465,7 +465,7 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.3", "@babel/parser@^7.1.6", "@babel/parser@^7.2.2", "@babel/parser@^7.3.4": +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.3", "@babel/parser@^7.1.6", "@babel/parser@^7.2.2", "@babel/parser@^7.3.4": version "7.3.4" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.4.tgz#a43357e4bbf4b92a437fb9e465c192848287f27c" integrity sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ== @@ -1463,6 +1463,39 @@ chalk "^2.0.1" slash "^2.0.0" +"@jest/core@^24.5.0": + version "24.5.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.5.0.tgz#2cefc6a69e9ebcae1da8f7c75f8a257152ba1ec0" + integrity sha512-RDZArRzAs51YS7dXG1pbXbWGxK53rvUu8mCDYsgqqqQ6uSOaTjcVyBl2Jce0exT2rSLk38ca7az7t2f3b0/oYQ== + dependencies: + "@jest/console" "^24.3.0" + "@jest/reporters" "^24.5.0" + "@jest/test-result" "^24.5.0" + "@jest/transform" "^24.5.0" + "@jest/types" "^24.5.0" + ansi-escapes "^3.0.0" + chalk "^2.0.1" + exit "^0.1.2" + graceful-fs "^4.1.15" + jest-changed-files "^24.5.0" + jest-config "^24.5.0" + jest-haste-map "^24.5.0" + jest-message-util "^24.5.0" + jest-regex-util "^24.3.0" + jest-resolve-dependencies "^24.5.0" + jest-runner "^24.5.0" + jest-runtime "^24.5.0" + jest-snapshot "^24.5.0" + jest-util "^24.5.0" + jest-validate "^24.5.0" + jest-watcher "^24.5.0" + micromatch "^3.1.10" + p-each-series "^1.0.0" + pirates "^4.0.1" + realpath-native "^1.1.0" + rimraf "^2.5.4" + strip-ansi "^5.0.0" + "@jest/environment@^24.5.0": version "24.5.0" resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.5.0.tgz#a2557f7808767abea3f9e4cc43a172122a63aca8" @@ -1484,6 +1517,32 @@ jest-message-util "^24.5.0" jest-mock "^24.5.0" +"@jest/reporters@^24.5.0": + version "24.5.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.5.0.tgz#9363a210d0daa74696886d9cb294eb8b3ad9b4d9" + integrity sha512-vfpceiaKtGgnuC3ss5czWOihKOUSyjJA4M4udm6nH8xgqsuQYcyDCi4nMMcBKsHXWgz9/V5G7iisnZGfOh1w6Q== + dependencies: + "@jest/environment" "^24.5.0" + "@jest/test-result" "^24.5.0" + "@jest/transform" "^24.5.0" + "@jest/types" "^24.5.0" + chalk "^2.0.1" + exit "^0.1.2" + glob "^7.1.2" + istanbul-api "^2.1.1" + istanbul-lib-coverage "^2.0.2" + istanbul-lib-instrument "^3.0.1" + istanbul-lib-source-maps "^3.0.1" + jest-haste-map "^24.5.0" + jest-resolve "^24.5.0" + jest-runtime "^24.5.0" + jest-util "^24.5.0" + jest-worker "^24.4.0" + node-notifier "^5.2.1" + slash "^2.0.0" + source-map "^0.6.0" + string-length "^2.0.0" + "@jest/source-map@^24.3.0": version "24.3.0" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.3.0.tgz#563be3aa4d224caf65ff77edc95cd1ca4da67f28" @@ -2411,7 +2470,33 @@ dependencies: "@types/estree" "*" -"@types/babel__traverse@^7.0.6": +"@types/babel__core@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.0.tgz#710f2487dda4dcfd010ca6abb2b4dc7394365c51" + integrity sha512-wJTeJRt7BToFx3USrCDs2BhEi4ijBInTQjOIukj6a/5tEkwpFMVZ+1ppgmE+Q/FQyc5P/VWUbx7I9NELrKruHA== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.0.2.tgz#d2112a6b21fad600d7674274293c85dce0cb47fc" + integrity sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307" + integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": version "7.0.6" resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.6.tgz#328dd1a8fc4cfe3c8458be9477b219ea158fd7b2" integrity sha512-XYVgHF2sQ0YblLRMLNPB3CkFMewzFmlDsH/TneZFHUXDlABQgh88uOxuez7ZcXxayLFrqLwtDH1t+FmlFwNZxw== @@ -2456,6 +2541,11 @@ resolved "https://registry.yarnpkg.com/@types/fbemitter/-/fbemitter-2.0.32.tgz#8ed204da0f54e9c8eaec31b1eec91e25132d082c" integrity sha1-jtIE2g9U6cjq7DGx7skeJRMtCCw= +"@types/history@*": + version "4.7.2" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.2.tgz#0e670ea254d559241b6eeb3894f8754991e73220" + integrity sha512-ui3WwXmjTaY73fOQ3/m3nnajU/Orhi6cEu5rzX+BrAAJxa3eITXZ5ch9suPqtM03OWhAHhPSyBGCN4UKoxO20Q== + "@types/invariant@^2.2.29": version "2.2.29" resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.29.tgz#aa845204cd0a289f65d47e0de63a6a815e30cc66" @@ -2487,6 +2577,27 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/lodash.isequal@^4.5.3": + version "4.5.3" + resolved "https://registry.yarnpkg.com/@types/lodash.isequal/-/lodash.isequal-4.5.3.tgz#b0d2747d3e76cc94da47ebd932c69a98c0ba61b4" + integrity sha512-tpTUmHksO2H9RF98Y2w7v06ZeEKAxHPo2ysL0bV5qv5UBweiZl33NFu5QYmYOSxSnHMqBt/vsVsBVeQAcJiokg== + dependencies: + "@types/lodash" "*" + +"@types/lodash.mergewith@^4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@types/lodash.mergewith/-/lodash.mergewith-4.6.4.tgz#8871c96d6a25274f2e27a95e92f3c8df41cfa5b5" + integrity sha512-3n2JrpWirSGbsXizIcgOzIOoa6JkjwG14gGRrNkMOB3iOrHfToP2rmnpAGbbXICM8Ew+8INXQkcrZX/HrpzVeA== + dependencies: + "@types/lodash" "*" + +"@types/lodash.pick@^4.4.4": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@types/lodash.pick/-/lodash.pick-4.4.4.tgz#381ac6c0a92f50405e2a6f9caeff07b0e40a9f75" + integrity sha512-54nf0ndJlN3ULz9fLceKhYJZfwf50jAqqPJyI3/UbU/qxyuMSgNC3niWhFqmVAnGPoxMwAwNQCYTXG0Gv2lfPg== + dependencies: + "@types/lodash" "*" + "@types/lodash.zipobject@^4.1.4": version "4.1.6" resolved "https://registry.yarnpkg.com/@types/lodash.zipobject/-/lodash.zipobject-4.1.6.tgz#75e140f44ac7d7682a18d3aae8ee4594fad094d7" @@ -2556,6 +2667,14 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.5.2.tgz#7f347062655056662845ba4bb79dcbfdc382cd61" integrity sha512-47kAAs3yV/hROraCTQYDMh4p/6zI9+gtssjD0kq9OWsGdLcBge59rl49FnCuJ+iWxEKiqFz6KXzeGH5DRVjNJA== +"@types/reach__router@^1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@types/reach__router/-/reach__router-1.2.3.tgz#679fa6968aab0314dc4b967808a23fd11af2ccce" + integrity sha512-Zp0AdVhoJXjwsgp8pDPVEMnAH5eHU64hi5EnPT1Jerddqwiy0O87KFrnZKd1DKdO87cU120n2d3SnKKPtf4wFA== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-dom@^16.8.2": version "16.8.2" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.8.2.tgz#9bd7d33f908b243ff0692846ef36c81d4941ad12" @@ -2675,6 +2794,11 @@ resolved "https://registry.yarnpkg.com/@types/websql/-/websql-0.0.27.tgz#621a666a7f02018e7cbb4abab956a25736c27d71" integrity sha1-Yhpman8CAY58u0q6uVaiVzbCfXE= +"@types/yargs@^12.0.2": + version "12.0.10" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.10.tgz#17a8ec65cd8e88f51b418ceb271af18d3137df67" + integrity sha512-WsVzTPshvCSbHThUduGGxbmnwcpkgSctHGHTqzWyFg4lYAuV5qXlyFPOsP3OWqCINfmg/8VXP+zJaa4OxEsBQQ== + "@types/yargs@^12.0.9": version "12.0.9" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0" @@ -3204,7 +3328,7 @@ acorn-dynamic-import@^4.0.0: resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948" integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw== -acorn-globals@^4.1.0: +acorn-globals@^4.1.0, acorn-globals@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.0.tgz#e3b6f8da3c1552a95ae627571f7dd6923bb54103" integrity sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw== @@ -3249,7 +3373,7 @@ acorn@^5.0.0, acorn@^5.5.0, acorn@^5.5.3, acorn@^5.6.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== -acorn@^6.0.1, acorn@^6.0.2, acorn@^6.0.5, acorn@^6.0.7: +acorn@^6.0.1, acorn@^6.0.2, acorn@^6.0.4, acorn@^6.0.5, acorn@^6.0.7: version "6.1.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA== @@ -3530,6 +3654,13 @@ append-transform@^0.4.0: dependencies: default-require-extensions "^1.0.0" +append-transform@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" + integrity sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw== + dependencies: + default-require-extensions "^2.0.0" + aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -3869,7 +4000,7 @@ async@^1.5.2: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= -async@^2.1.4, async@^2.4.0, async@^2.4.1, async@^2.5.0, async@^2.6.0: +async@^2.1.4, async@^2.4.0, async@^2.4.1, async@^2.5.0, async@^2.6.0, async@^2.6.1: version "2.6.2" resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381" integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg== @@ -4209,6 +4340,19 @@ babel-jest@^24.1.0: chalk "^2.4.2" slash "^2.0.0" +babel-jest@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.5.0.tgz#0ea042789810c2bec9065f7c8ab4dc18e1d28559" + integrity sha512-0fKCXyRwxFTJL0UXDJiT2xYxO9Lu2vBd9n+cC+eDjESzcVG3s2DRGAxbzJX21fceB1WYoBjAh8pQ83dKcl003g== + dependencies: + "@jest/transform" "^24.5.0" + "@jest/types" "^24.5.0" + "@types/babel__core" "^7.1.0" + babel-plugin-istanbul "^5.1.0" + babel-preset-jest "^24.3.0" + chalk "^2.4.2" + slash "^2.0.0" + babel-loader@8.0.5, babel-loader@^8, babel-loader@^8.0.4, babel-loader@^8.0.5: version "8.0.5" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.5.tgz#225322d7509c2157655840bba52e46b6c2f2fe33" @@ -4350,6 +4494,13 @@ babel-plugin-jest-hoist@^24.1.0: dependencies: "@types/babel__traverse" "^7.0.6" +babel-plugin-jest-hoist@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.3.0.tgz#f2e82952946f6e40bb0a75d266a3790d854c8b5b" + integrity sha512-nWh4N1mVH55Tzhx2isvUN5ebM5CDUvIpXPZYMRazQughie/EqGnbR+czzoQlhUmJG9pPJmYDRhvocotb2THl1w== + dependencies: + "@types/babel__traverse" "^7.0.6" + babel-plugin-jsx-event-modifiers@^2.0.2: version "2.0.5" resolved "https://registry.yarnpkg.com/babel-plugin-jsx-event-modifiers/-/babel-plugin-jsx-event-modifiers-2.0.5.tgz#93e6ebb5d7553bb08f9fedbf7a0bee3af09a0472" @@ -5010,6 +5161,14 @@ babel-preset-jest@^24.1.0: "@babel/plugin-syntax-object-rest-spread" "^7.0.0" babel-plugin-jest-hoist "^24.1.0" +babel-preset-jest@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.3.0.tgz#db88497e18869f15b24d9c0e547d8e0ab950796d" + integrity sha512-VGTV2QYBa/Kn3WCOKdfS31j9qomaXSgJqi65B6o05/1GsJyj9LVhSljM9ro4S+IBGj/ENhNBuH9bpqzztKAQSw== + dependencies: + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + babel-plugin-jest-hoist "^24.3.0" + "babel-preset-minify@^0.5.0 || 0.6.0-alpha.5": version "0.5.0" resolved "https://registry.yarnpkg.com/babel-preset-minify/-/babel-preset-minify-0.5.0.tgz#e25bb8d3590087af02b650967159a77c19bfb96b" @@ -5268,6 +5427,13 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.2.tgz#c83c3d74233ba7674e4f313cb2a2b70f54e94b7c" integrity sha512-xVNN69YGDghOqCCtA6FI7avYrr02mTJjOgB0/f1VPD3pJC8QEvjTKWc4epDx8AqxxA75NI0QpVM2gPJXUbE4Tg== +bindings@^1.2.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + bl@^1.0.0: version "1.2.2" resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" @@ -7011,6 +7177,11 @@ compare-func@^1.3.1: array-ify "^1.0.0" dot-prop "^3.0.0" +compare-versions@^3.2.1: + version "3.4.0" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.4.0.tgz#e0747df5c9cb7f054d6d3dc3e1dbc444f9e92b26" + integrity sha512-tK69D7oNXXqUW3ZNo/z7NXTEz22TCF0pTE+YF9cxvaAM9XnkLo1fV621xCLrRR6aevJlKxExkss0vWqUCUpqdg== + complain@^1.0.0, complain@^1.2.0, complain@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/complain/-/complain-1.3.0.tgz#a182d5003489126c038bbf98b906866acbfa77b2" @@ -7826,12 +7997,12 @@ csso@^3.5.1: dependencies: css-tree "1.0.0-alpha.29" -cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": +cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0", cssom@^0.3.4: version "0.3.6" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.6.tgz#f85206cee04efa841f3c5982a74ba96ab20d65ad" integrity sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A== -cssstyle@^1.0.0: +cssstyle@^1.0.0, cssstyle@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.2.1.tgz#3aceb2759eaf514ac1a21628d723d6043a819495" integrity sha512-7DYm8qe+gPx/h77QlCyFmX80+fGaE/6A/Ekl0zaszYOubvySO2saYFdQ78P29D0UsULxFKCetDGNaNRUdSF+2A== @@ -7929,7 +8100,7 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -data-urls@^1.0.0: +data-urls@^1.0.0, data-urls@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== @@ -8107,6 +8278,13 @@ default-require-extensions@^1.0.0: dependencies: strip-bom "^2.0.0" +default-require-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7" + integrity sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc= + dependencies: + strip-bom "^3.0.0" + defaults@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" @@ -8305,6 +8483,11 @@ diff-sequences@^24.0.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.2.0.tgz#605025e678673636d82c49eda94a9cebcbf4b648" integrity sha512-yvZNXjhIhe9DwBOKfdr1HKmvld95EzQkZOUjlZlPzzIBy01TM8oDweo2PT9WJmaHd8+J7DC8etgbJGHWWuVJxQ== +diff-sequences@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975" + integrity sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw== + diff@^3.1.0, diff@^3.2.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -9310,7 +9493,7 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@^1.10.0, escodegen@^1.8.1, escodegen@^1.9.1: +escodegen@^1.10.0, escodegen@^1.11.0, escodegen@^1.8.1, escodegen@^1.9.1: version "1.11.1" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.1.tgz#c485ff8d6b4cdb89e27f4a856e91f118401ca510" integrity sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw== @@ -9874,6 +10057,18 @@ expect@^24.1.0: jest-message-util "^24.0.0" jest-regex-util "^24.0.0" +expect@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-24.5.0.tgz#492fb0df8378d8474cc84b827776b069f46294ed" + integrity sha512-p2Gmc0CLxOgkyA93ySWmHFYHUPFIHG6XZ06l7WArWAsrqYVaVEkOU5NtT5i68KUyGKbkQgDCkiT65bWmdoL6Bw== + dependencies: + "@jest/types" "^24.5.0" + ansi-styles "^3.2.0" + jest-get-type "^24.3.0" + jest-matcher-utils "^24.5.0" + jest-message-util "^24.5.0" + jest-regex-util "^24.3.0" + expo-ads-admob@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/expo-ads-admob/-/expo-ads-admob-2.0.0.tgz#53b78a5ac5eee4f306ff461256160990ab96936b" @@ -10643,12 +10838,17 @@ file-system-cache@^1.0.5: fs-extra "^0.30.0" ramda "^0.21.0" +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= -fileset@^2.0.2: +fileset@^2.0.2, fileset@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0" integrity sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA= @@ -13261,6 +13461,25 @@ istanbul-api@^1.3.1: mkdirp "^0.5.1" once "^1.4.0" +istanbul-api@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-2.1.1.tgz#194b773f6d9cbc99a9258446848b0f988951c4d0" + integrity sha512-kVmYrehiwyeBAk/wE71tW6emzLiHGjYIiDrc8sfyty4F8M02/lrgXSm+R1kXysmF20zArvmZXjlE/mg24TVPJw== + dependencies: + async "^2.6.1" + compare-versions "^3.2.1" + fileset "^2.0.3" + istanbul-lib-coverage "^2.0.3" + istanbul-lib-hook "^2.0.3" + istanbul-lib-instrument "^3.1.0" + istanbul-lib-report "^2.0.4" + istanbul-lib-source-maps "^3.0.2" + istanbul-reports "^2.1.1" + js-yaml "^3.12.0" + make-dir "^1.3.0" + minimatch "^3.0.4" + once "^1.4.0" + istanbul-instrumenter-loader@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz#9957bd59252b373fae5c52b7b5188e6fde2a0949" @@ -13276,7 +13495,7 @@ istanbul-lib-coverage@^1.2.0, istanbul-lib-coverage@^1.2.1: resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz#ccf7edcd0a0bb9b8f729feeb0930470f9af664f0" integrity sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ== -istanbul-lib-coverage@^2.0.3: +istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#0b891e5ad42312c2b9488554f603795f9a2211ba" integrity sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw== @@ -13288,6 +13507,13 @@ istanbul-lib-hook@^1.2.2: dependencies: append-transform "^0.4.0" +istanbul-lib-hook@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.3.tgz#e0e581e461c611be5d0e5ef31c5f0109759916fb" + integrity sha512-CLmEqwEhuCYtGcpNVJjLV1DQyVnIqavMLFHV/DP+np/g3qvdxu3gsPqYoJMXm15sN84xOlckFB3VNvRbf5yEgA== + dependencies: + append-transform "^1.0.0" + istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.10.2, istanbul-lib-instrument@^1.7.3: version "1.10.2" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz#1f55ed10ac3c47f2bdddd5307935126754d0a9ca" @@ -13301,7 +13527,7 @@ istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.10.2, istanbul-lib-i istanbul-lib-coverage "^1.2.1" semver "^5.3.0" -istanbul-lib-instrument@^3.0.0: +istanbul-lib-instrument@^3.0.0, istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz#a2b5484a7d445f1f311e93190813fa56dfb62971" integrity sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA== @@ -13324,6 +13550,15 @@ istanbul-lib-report@^1.1.5: path-parse "^1.0.5" supports-color "^3.1.2" +istanbul-lib-report@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.4.tgz#bfd324ee0c04f59119cb4f07dab157d09f24d7e4" + integrity sha512-sOiLZLAWpA0+3b5w5/dq0cjm2rrNdAfHWaGhmn7XEFW6X++IV9Ohn+pnELAl9K3rfpaeBfbmH9JU5sejacdLeA== + dependencies: + istanbul-lib-coverage "^2.0.3" + make-dir "^1.3.0" + supports-color "^6.0.0" + istanbul-lib-source-maps@^1.2.4, istanbul-lib-source-maps@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz#37b9ff661580f8fca11232752ee42e08c6675d8f" @@ -13335,6 +13570,17 @@ istanbul-lib-source-maps@^1.2.4, istanbul-lib-source-maps@^1.2.6: rimraf "^2.6.1" source-map "^0.5.3" +istanbul-lib-source-maps@^3.0.1, istanbul-lib-source-maps@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.2.tgz#f1e817229a9146e8424a28e5d69ba220fda34156" + integrity sha512-JX4v0CiKTGp9fZPmoxpu9YEkPbEqCqBbO3403VabKjH+NRXo72HafD5UgnjTEqHL2SAjaZK1XDuDOkn6I5QVfQ== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^2.0.3" + make-dir "^1.3.0" + rimraf "^2.6.2" + source-map "^0.6.1" + istanbul-reports@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.5.1.tgz#97e4dbf3b515e8c484caea15d6524eebd3ff4e1a" @@ -13342,6 +13588,13 @@ istanbul-reports@^1.5.1: dependencies: handlebars "^4.0.3" +istanbul-reports@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.1.1.tgz#72ef16b4ecb9a4a7bd0e2001e00f95d1eec8afa9" + integrity sha512-FzNahnidyEPBCI0HcufJoSEoKykesRlFcSzQqjH9x0+LC8tnnE/p/90PBLu8iZTxr8yYZNyTtiAujUqyN+CIxw== + dependencies: + handlebars "^4.1.0" + istextorbinary@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-2.1.0.tgz#dbed2a6f51be2f7475b68f89465811141b758874" @@ -13407,6 +13660,15 @@ jest-changed-files@^23.4.2: dependencies: throat "^4.0.0" +jest-changed-files@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.5.0.tgz#4075269ee115d87194fd5822e642af22133cf705" + integrity sha512-Ikl29dosYnTsH9pYa1Tv9POkILBhN/TLZ37xbzgNsZ1D2+2n+8oEZS2yP1BrHn/T4Rs4Ggwwbp/x8CKOS5YJOg== + dependencies: + "@jest/types" "^24.5.0" + execa "^1.0.0" + throat "^4.0.0" + jest-cli@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-23.6.0.tgz#61ab917744338f443ef2baa282ddffdd658a5da4" @@ -13449,6 +13711,25 @@ jest-cli@^23.6.0: which "^1.2.12" yargs "^11.0.0" +jest-cli@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.5.0.tgz#598139d3446d1942fb7dc93944b9ba766d756d4b" + integrity sha512-P+Jp0SLO4KWN0cGlNtC7JV0dW1eSFR7eRpoOucP2UM0sqlzp/bVHeo71Omonvigrj9AvCKy7NtQANtqJ7FXz8g== + dependencies: + "@jest/core" "^24.5.0" + "@jest/test-result" "^24.5.0" + "@jest/types" "^24.5.0" + chalk "^2.0.1" + exit "^0.1.2" + import-local "^2.0.0" + is-ci "^2.0.0" + jest-config "^24.5.0" + jest-util "^24.5.0" + jest-validate "^24.5.0" + prompts "^2.0.1" + realpath-native "^1.1.0" + yargs "^12.0.2" + jest-config@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-23.6.0.tgz#f82546a90ade2d8c7026fbf6ac5207fc22f8eb1d" @@ -13490,6 +13771,28 @@ jest-config@^24.0.0: pretty-format "^24.0.0" realpath-native "^1.0.2" +jest-config@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.5.0.tgz#404d1bc6bb81aed6bd1890d07e2dca9fbba2e121" + integrity sha512-t2UTh0Z2uZhGBNVseF8wA2DS2SuBiLOL6qpLq18+OZGfFUxTM7BzUVKyHFN/vuN+s/aslY1COW95j1Rw81huOQ== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^24.5.0" + babel-jest "^24.5.0" + chalk "^2.0.1" + glob "^7.1.1" + jest-environment-jsdom "^24.5.0" + jest-environment-node "^24.5.0" + jest-get-type "^24.3.0" + jest-jasmine2 "^24.5.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.5.0" + jest-util "^24.5.0" + jest-validate "^24.5.0" + micromatch "^3.1.10" + pretty-format "^24.5.0" + realpath-native "^1.1.0" + jest-diff@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-23.6.0.tgz#1500f3f16e850bb3d71233408089be099f610c7d" @@ -13510,6 +13813,16 @@ jest-diff@^24.0.0: jest-get-type "^24.0.0" pretty-format "^24.0.0" +jest-diff@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.5.0.tgz#a2d8627964bb06a91893c0fbcb28ab228c257652" + integrity sha512-mCILZd9r7zqL9Uh6yNoXjwGQx0/J43OD2vvWVKwOEOLZliQOsojXwqboubAQ+Tszrb6DHGmNU7m4whGeB9YOqw== + dependencies: + chalk "^2.0.1" + diff-sequences "^24.3.0" + jest-get-type "^24.3.0" + pretty-format "^24.5.0" + jest-docblock@23.2.0, jest-docblock@^23.2.0: version "23.2.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-23.2.0.tgz#f085e1f18548d99fdd69b20207e6fd55d91383a7" @@ -13522,6 +13835,13 @@ jest-docblock@^21.0.0: resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.2.0.tgz#51529c3b30d5fd159da60c27ceedc195faf8d414" integrity sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw== +jest-docblock@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.3.0.tgz#b9c32dac70f72e4464520d2ba4aec02ab14db5dd" + integrity sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg== + dependencies: + detect-newline "^2.1.0" + jest-each@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-23.6.0.tgz#ba0c3a82a8054387016139c733a05242d3d71575" @@ -13540,6 +13860,17 @@ jest-each@^24.0.0: jest-util "^24.0.0" pretty-format "^24.0.0" +jest-each@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.5.0.tgz#da14d017a1b7d0f01fb458d338314cafe7f72318" + integrity sha512-6gy3Kh37PwIT5sNvNY2VchtIFOOBh8UCYnBlxXMb5sr5wpJUDPTUATX2Axq1Vfk+HWTMpsYPeVYp4TXx5uqUBw== + dependencies: + "@jest/types" "^24.5.0" + chalk "^2.0.1" + jest-get-type "^24.3.0" + jest-util "^24.5.0" + pretty-format "^24.5.0" + jest-emotion@^10.0.7: version "10.0.7" resolved "https://registry.yarnpkg.com/jest-emotion/-/jest-emotion-10.0.7.tgz#c882f6e201cf62c025245f863b8e5ff473d2cef0" @@ -13557,6 +13888,15 @@ jest-environment-enzyme@^7.0.2: dependencies: jest-environment-jsdom "^24.0.0" +jest-environment-jsdom-thirteen@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom-thirteen/-/jest-environment-jsdom-thirteen-1.0.0.tgz#24eac0718c5792edef269232b5bac3cf82b18911" + integrity sha512-DOlBE/tUgb8okcOcehf8Yt5U9o/QlkLoGS9HwKZtzKiKrntyCAb12FTmuBfEpjZXsA/p559Kuw0PbSh5GzEcjw== + dependencies: + jest-mock "^24.0.0" + jest-util "^24.0.0" + jsdom "^13.0.0" + jest-environment-jsdom@^23.4.0: version "23.4.0" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz#056a7952b3fea513ac62a140a2c368c79d9e6023" @@ -13594,6 +13934,17 @@ jest-environment-node@^24.0.0: jest-mock "^24.0.0" jest-util "^24.0.0" +jest-environment-node@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.5.0.tgz#763eebdf529f75b60aa600c6cf8cb09873caa6ab" + integrity sha512-du6FuyWr/GbKLsmAbzNF9mpr2Iu2zWSaq/BNHzX+vgOcts9f2ayXBweS7RAhr+6bLp6qRpMB6utAMF5Ygktxnw== + dependencies: + "@jest/environment" "^24.5.0" + "@jest/fake-timers" "^24.5.0" + "@jest/types" "^24.5.0" + jest-mock "^24.5.0" + jest-util "^24.5.0" + jest-enzyme@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/jest-enzyme/-/jest-enzyme-7.0.2.tgz#706af4d713846d5cb78c24830cf3deed9bb029a1" @@ -13625,6 +13976,11 @@ jest-get-type@^24.0.0: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.2.0.tgz#00fc17a3ad08b68b6ab14ca47d4f949a82425e76" integrity sha512-IWrgF05BU05R7vXs+UfL9NbPfInotQWZMWv5T/jU3h/wH7qg2GGZ/hqR7zA/+n6TtWvT/E2vBxCkB/3s6pHrIg== +jest-get-type@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.3.0.tgz#582cfd1a4f91b5cdad1d43d2932f816d543c65da" + integrity sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow== + jest-haste-map@23.5.0: version "23.5.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-23.5.0.tgz#d4ca618188bd38caa6cb20349ce6610e194a8065" @@ -13717,6 +14073,28 @@ jest-jasmine2@^24.1.0: pretty-format "^24.0.0" throat "^4.0.0" +jest-jasmine2@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.5.0.tgz#e6af4d7f73dc527d007cca5a5b177c0bcc29d111" + integrity sha512-sfVrxVcx1rNUbBeyIyhkqZ4q+seNKyAG6iM0S2TYBdQsXjoFDdqWFfsUxb6uXSsbimbXX/NMkJIwUZ1uT9+/Aw== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^24.5.0" + "@jest/test-result" "^24.5.0" + "@jest/types" "^24.5.0" + chalk "^2.0.1" + co "^4.6.0" + expect "^24.5.0" + is-generator-fn "^2.0.0" + jest-each "^24.5.0" + jest-matcher-utils "^24.5.0" + jest-message-util "^24.5.0" + jest-runtime "^24.5.0" + jest-snapshot "^24.5.0" + jest-util "^24.5.0" + pretty-format "^24.5.0" + throat "^4.0.0" + jest-leak-detector@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz#e4230fd42cf381a1a1971237ad56897de7e171de" @@ -13724,6 +14102,13 @@ jest-leak-detector@^23.6.0: dependencies: pretty-format "^23.6.0" +jest-leak-detector@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.5.0.tgz#21ae2b3b0da252c1171cd494f75696d65fb6fa89" + integrity sha512-LZKBjGovFRx3cRBkqmIg+BZnxbrLqhQl09IziMk3oeh1OV81Hg30RUIx885mq8qBv1PA0comB9bjKcuyNO1bCQ== + dependencies: + pretty-format "^24.5.0" + jest-matcher-utils@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz#726bcea0c5294261a7417afb6da3186b4b8cac80" @@ -13743,6 +14128,16 @@ jest-matcher-utils@^24.0.0: jest-get-type "^24.0.0" pretty-format "^24.0.0" +jest-matcher-utils@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.5.0.tgz#5995549dcf09fa94406e89526e877b094dad8770" + integrity sha512-QM1nmLROjLj8GMGzg5VBra3I9hLpjMPtF1YqzQS3rvWn2ltGZLrGAO1KQ9zUCVi5aCvrkbS5Ndm2evIP9yZg1Q== + dependencies: + chalk "^2.0.1" + jest-diff "^24.5.0" + jest-get-type "^24.3.0" + pretty-format "^24.5.0" + jest-message-util@^23.4.0: version "23.4.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-23.4.0.tgz#17610c50942349508d01a3d1e0bda2c079086a9f" @@ -13796,6 +14191,11 @@ jest-pnp-resolver@1.0.2: resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.0.2.tgz#470384ae9ea31f72136db52618aa4010ff23b715" integrity sha512-H2DvUlwdMedNGv4FOliPDnxani6ATWy70xe2eckGJgkLoMaWzRPqpSlc5ShqX0Ltk5OhRQvPQY2LLZPOpgcc7g== +jest-pnp-resolver@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" + integrity sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ== + jest-preset-angular@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/jest-preset-angular/-/jest-preset-angular-6.0.2.tgz#a5c79d6c1b1f894fa12f9dd07bab9506ef9737c1" @@ -13833,6 +14233,15 @@ jest-resolve-dependencies@^23.6.0: jest-regex-util "^23.3.0" jest-snapshot "^23.6.0" +jest-resolve-dependencies@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.5.0.tgz#1a0dae9cdd41349ca4a84148b3e78da2ba33fd4b" + integrity sha512-dRVM1D+gWrFfrq2vlL5P9P/i8kB4BOYqYf3S7xczZ+A6PC3SgXYSErX/ScW/469pWMboM1uAhgLF+39nXlirCQ== + dependencies: + "@jest/types" "^24.5.0" + jest-regex-util "^24.3.0" + jest-snapshot "^24.5.0" + jest-resolve@23.6.0, jest-resolve@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-23.6.0.tgz#cf1d1a24ce7ee7b23d661c33ba2150f3aebfa0ae" @@ -13851,6 +14260,17 @@ jest-resolve@^24.1.0: chalk "^2.0.1" realpath-native "^1.0.0" +jest-resolve@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.5.0.tgz#8c16ba08f60a1616c3b1cd7afb24574f50a24d04" + integrity sha512-ZIfGqLX1Rg8xJpQqNjdoO8MuxHV1q/i2OO1hLXjgCWFWs5bsedS8UrOdgjUqqNae6DXA+pCyRmdcB7lQEEbXew== + dependencies: + "@jest/types" "^24.5.0" + browser-resolve "^1.11.3" + chalk "^2.0.1" + jest-pnp-resolver "^1.2.1" + realpath-native "^1.1.0" + jest-runner@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-23.6.0.tgz#3894bd219ffc3f3cb94dc48a4170a2e6f23a5a38" @@ -13870,6 +14290,31 @@ jest-runner@^23.6.0: source-map-support "^0.5.6" throat "^4.0.0" +jest-runner@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.5.0.tgz#9be26ece4fd4ab3dfb528b887523144b7c5ffca8" + integrity sha512-oqsiS9TkIZV5dVkD+GmbNfWBRPIvxqmlTQ+AQUJUQ07n+4xTSDc40r+aKBynHw9/tLzafC00DIbJjB2cOZdvMA== + dependencies: + "@jest/console" "^24.3.0" + "@jest/environment" "^24.5.0" + "@jest/test-result" "^24.5.0" + "@jest/types" "^24.5.0" + chalk "^2.4.2" + exit "^0.1.2" + graceful-fs "^4.1.15" + jest-config "^24.5.0" + jest-docblock "^24.3.0" + jest-haste-map "^24.5.0" + jest-jasmine2 "^24.5.0" + jest-leak-detector "^24.5.0" + jest-message-util "^24.5.0" + jest-resolve "^24.5.0" + jest-runtime "^24.5.0" + jest-util "^24.5.0" + jest-worker "^24.4.0" + source-map-support "^0.5.6" + throat "^4.0.0" + jest-runtime@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-23.6.0.tgz#059e58c8ab445917cd0e0d84ac2ba68de8f23082" @@ -13897,6 +14342,35 @@ jest-runtime@^23.6.0: write-file-atomic "^2.1.0" yargs "^11.0.0" +jest-runtime@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.5.0.tgz#3a76e0bfef4db3896d5116e9e518be47ba771aa2" + integrity sha512-GTFHzfLdwpaeoDPilNpBrorlPoNZuZrwKKzKJs09vWwHo+9TOsIIuszK8cWOuKC7ss07aN1922Ge8fsGdsqCuw== + dependencies: + "@jest/console" "^24.3.0" + "@jest/environment" "^24.5.0" + "@jest/source-map" "^24.3.0" + "@jest/transform" "^24.5.0" + "@jest/types" "^24.5.0" + "@types/yargs" "^12.0.2" + chalk "^2.0.1" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.1.15" + jest-config "^24.5.0" + jest-haste-map "^24.5.0" + jest-message-util "^24.5.0" + jest-mock "^24.5.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.5.0" + jest-snapshot "^24.5.0" + jest-util "^24.5.0" + jest-validate "^24.5.0" + realpath-native "^1.1.0" + slash "^2.0.0" + strip-bom "^3.0.0" + yargs "^12.0.2" + jest-serializer@23.0.1, jest-serializer@^23.0.1: version "23.0.1" resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-23.0.1.tgz#a3776aeb311e90fe83fab9e533e85102bd164165" @@ -13939,6 +14413,24 @@ jest-snapshot@^24.1.0: pretty-format "^24.0.0" semver "^5.5.0" +jest-snapshot@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.5.0.tgz#e5d224468a759fd19e36f01217aac912f500f779" + integrity sha512-eBEeJb5ROk0NcpodmSKnCVgMOo+Qsu5z9EDl3tGffwPzK1yV37mjGWF2YeIz1NkntgTzP+fUL4s09a0+0dpVWA== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^24.5.0" + chalk "^2.0.1" + expect "^24.5.0" + jest-diff "^24.5.0" + jest-matcher-utils "^24.5.0" + jest-message-util "^24.5.0" + jest-resolve "^24.5.0" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + pretty-format "^24.5.0" + semver "^5.5.0" + jest-specific-snapshot@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/jest-specific-snapshot/-/jest-specific-snapshot-1.1.0.tgz#3eaa12a79105ebe73453e2e174c4c0014445d890" @@ -14005,6 +14497,18 @@ jest-validate@^24.0.0: leven "^2.1.0" pretty-format "^24.0.0" +jest-validate@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.5.0.tgz#62fd93d81214c070bb2d7a55f329a79d8057c7de" + integrity sha512-gg0dYszxjgK2o11unSIJhkOFZqNRQbWOAB2/LOUdsd2LfD9oXiMeuee8XsT0iRy5EvSccBgB4h/9HRbIo3MHgQ== + dependencies: + "@jest/types" "^24.5.0" + camelcase "^5.0.0" + chalk "^2.0.1" + jest-get-type "^24.3.0" + leven "^2.1.0" + pretty-format "^24.5.0" + jest-vue-preprocessor@^1.4.0: version "1.5.0" resolved "https://registry.yarnpkg.com/jest-vue-preprocessor/-/jest-vue-preprocessor-1.5.0.tgz#242b1484e6fc832c15a95f28d108e022ee9f1e08" @@ -14036,6 +14540,20 @@ jest-watcher@^23.1.0, jest-watcher@^23.4.0: chalk "^2.0.1" string-length "^2.0.0" +jest-watcher@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.5.0.tgz#da7bd9cb5967e274889b42078c8f501ae1c47761" + integrity sha512-/hCpgR6bg0nKvD3nv4KasdTxuhwfViVMHUATJlnGCD0r1QrmIssimPbmc5KfAQblAVxkD8xrzuij9vfPUk1/rA== + dependencies: + "@jest/test-result" "^24.5.0" + "@jest/types" "^24.5.0" + "@types/node" "*" + "@types/yargs" "^12.0.9" + ansi-escapes "^3.0.0" + chalk "^2.0.1" + jest-util "^24.5.0" + string-length "^2.0.0" + jest-worker@23.2.0, jest-worker@^23.2.0: version "23.2.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-23.2.0.tgz#faf706a8da36fae60eb26957257fa7b5d8ea02b9" @@ -14065,6 +14583,14 @@ jest@23.6.0, jest@^23.6.0: import-local "^1.0.0" jest-cli "^23.6.0" +jest@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-24.5.0.tgz#38f11ae2c2baa2f86c2bc4d8a91d2b51612cd19a" + integrity sha512-lxL+Fq5/RH7inxxmfS2aZLCf8MsS+YCUBfeiNO6BWz/MmjhDGaIEA/2bzEf9q4Q0X+mtFHiinHFvQ0u+RvW/qQ== + dependencies: + import-local "^2.0.0" + jest-cli "^24.5.0" + joi@^11.1.1: version "11.4.0" resolved "https://registry.yarnpkg.com/joi/-/joi-11.4.0.tgz#f674897537b625e9ac3d0b7e1604c828ad913ccb" @@ -14184,6 +14710,38 @@ jsdom@^11.5.1: ws "^5.2.0" xml-name-validator "^3.0.0" +jsdom@^13.0.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-13.2.0.tgz#b1a0dbdadc255435262be8ea3723d2dba0d7eb3a" + integrity sha512-cG1NtMWO9hWpqRNRR3dSvEQa8bFI6iLlqU2x4kwX51FQjp0qus8T9aBaAO6iGp3DeBrhdwuKxckknohkmfvsFw== + dependencies: + abab "^2.0.0" + acorn "^6.0.4" + acorn-globals "^4.3.0" + array-equal "^1.0.0" + cssom "^0.3.4" + cssstyle "^1.1.1" + data-urls "^1.1.0" + domexception "^1.0.1" + escodegen "^1.11.0" + html-encoding-sniffer "^1.0.2" + nwsapi "^2.0.9" + parse5 "5.1.0" + pn "^1.1.0" + request "^2.88.0" + request-promise-native "^1.0.5" + saxes "^3.1.5" + symbol-tree "^3.2.2" + tough-cookie "^2.5.0" + w3c-hr-time "^1.0.1" + w3c-xmlserializer "^1.0.1" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^7.0.0" + ws "^6.1.2" + xml-name-validator "^3.0.0" + jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" @@ -14457,6 +15015,11 @@ kleur@^2.0.1: resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300" integrity sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ== +kleur@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.2.tgz#83c7ec858a41098b613d5998a7b653962b504f68" + integrity sha512-3h7B2WRT5LNXOtQiAaWonilegHcPSf9nLVXlSTci8lu1dZUuui61+EsPEZqSVxY7rXYmB2DVKMQILxaO5WL61Q== + labeled-stream-splicer@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/labeled-stream-splicer/-/labeled-stream-splicer-2.0.1.tgz#9cffa32fd99e1612fd1d86a8db962416d5292926" @@ -15659,7 +16222,7 @@ magic-string@^0.25.0: dependencies: sourcemap-codec "^1.4.4" -make-dir@^1.0.0: +make-dir@^1.0.0, make-dir@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== @@ -16891,6 +17454,11 @@ najax@^1.0.3: lodash.defaultsdeep "^4.6.0" qs "^6.2.0" +nan@^2.0.5: + version "2.13.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.1.tgz#a15bee3790bde247e8f38f1d446edcdaeb05f2dd" + integrity sha512-I6YB/YEuDeUZMmhscXKxGgZlFnhsn5y0hgOZBadkzfTRrZBtJDZeg6eQf7PYMIEclwmorTKK8GztsyOUSVBREA== + nan@^2.10.0, nan@^2.9.2: version "2.12.1" resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" @@ -17351,7 +17919,7 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= -nwsapi@^2.0.7: +nwsapi@^2.0.7, nwsapi@^2.0.9: version "2.1.1" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.1.1.tgz#08d6d75e69fd791bdea31507ffafe8c843b67e9c" integrity sha512-T5GaA1J/d34AC8mkrFD2O0DR17kwJ702ZOtJOsS8RpbsQZVOC2/xYFb1i/cw+xdM54JIlMuojjDOYct8GIWtwg== @@ -17677,6 +18245,13 @@ p-defer@^1.0.0: resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= +p-each-series@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71" + integrity sha1-kw89Et0fUOdDRFeiLNbwSsatf3E= + dependencies: + p-reduce "^1.0.0" + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -18021,6 +18596,11 @@ parse5@4.0.0: resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== +parse5@5.1.0, parse5@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" + integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== + parse5@^2.2.1, parse5@^2.2.2: version "2.2.3" resolved "https://registry.yarnpkg.com/parse5/-/parse5-2.2.3.tgz#0c4fc41c1000c5e6b93d48b03f8083837834e9f6" @@ -18033,11 +18613,6 @@ parse5@^3.0.1, parse5@^3.0.2: dependencies: "@types/node" "*" -parse5@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" - integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== - parseqs@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" @@ -18240,7 +18815,7 @@ pinpoint@^1.1.0: resolved "https://registry.yarnpkg.com/pinpoint/-/pinpoint-1.1.0.tgz#0cf7757a6977f1bf7f6a32207b709e377388e874" integrity sha1-DPd1eml38b9/ajIge3CeN3OI6HQ= -pirates@^4.0.0: +pirates@^4.0.0, pirates@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== @@ -19148,6 +19723,16 @@ pretty-format@^24.0.0: ansi-regex "^4.0.0" ansi-styles "^3.2.0" +pretty-format@^24.5.0: + version "24.5.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.5.0.tgz#cc69a0281a62cd7242633fc135d6930cd889822d" + integrity sha512-/3RuSghukCf8Riu5Ncve0iI+BzVkbRU5EeUoArKARZobREycuH5O4waxvaNIloEXdb0qwgmEAed5vTpX1HNROQ== + dependencies: + "@jest/types" "^24.5.0" + ansi-regex "^4.0.0" + ansi-styles "^3.2.0" + react-is "^16.8.4" + pretty-format@^4.2.1: version "4.3.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-4.3.1.tgz#530be5c42b3c05b36414a7a2a4337aa80acd0e8d" @@ -19260,6 +19845,14 @@ prompts@^0.1.9: kleur "^2.0.1" sisteransi "^0.1.1" +prompts@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.0.4.tgz#179f9d4db3128b9933aa35f93a800d8fce76a682" + integrity sha512-HTzM3UWp/99A0gk51gAegwo1QRYA7xjcZufMNe33rCclFszUYAuHe1fIN/3ZmiHeGPkUsNaRyQm1hHOfM0PKxA== + dependencies: + kleur "^3.0.2" + sisteransi "^1.0.0" + promzard@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee" @@ -20347,7 +20940,7 @@ react@16.5.1: prop-types "^15.6.2" schedule "^0.4.0" -react@^16.6.0, react@^16.8.4: +react@^16.6.0, react@^16.7.0, react@^16.8.4: version "16.8.4" resolved "https://registry.yarnpkg.com/react/-/react-16.8.4.tgz#fdf7bd9ae53f03a9c4cd1a371432c206be1c4768" integrity sha512-0GQ6gFXfUH7aZcjGVymlPOASTuSjlQL4ZtVC5YKH+3JL6bBLCVO21DknzmaPlI90LN253ojj02nsapy+j7wIjg== @@ -21638,6 +22231,13 @@ sax@~1.1.1: resolved "https://registry.yarnpkg.com/sax/-/sax-1.1.6.tgz#5d616be8a5e607d54e114afae55b7eaf2fcc3240" integrity sha1-XWFr6KXmB9VOEUr65Vt+ry/MMkA= +saxes@^3.1.5: + version "3.1.9" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.9.tgz#c1c197cd54956d88c09f960254b999e192d7058b" + integrity sha512-FZeKhJglhJHk7eWG5YM0z46VHmI3KJpMBAQm3xa9meDvd+wevB5GuBB0wc0exPInZiBBHqi00DbS8AcvCGCFMw== + dependencies: + xmlchars "^1.3.1" + schedule@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/schedule/-/schedule-0.4.0.tgz#fa20cfd0bfbf91c47d02272fd7096780d3170bbb" @@ -22039,6 +22639,11 @@ sisteransi@^0.1.1: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-0.1.1.tgz#5431447d5f7d1675aac667ccd0b865a4994cb3ce" integrity sha512-PmGOd02bM9YO5ifxpw36nrNMBTptEtfRl4qUYl9SndkolplkrZZOW7PGHjrZL53QvMVj9nQ+TKqUnRsw4tJa4g== +sisteransi@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.0.tgz#77d9622ff909080f1c19e5f4a1df0c1b0a27b88c" + integrity sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ== + skip-regex@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/skip-regex/-/skip-regex-0.3.1.tgz#17919aae2ac4ce3d61d5e77eedd88206c64aa215" @@ -22917,7 +23522,7 @@ supports-color@^5.0.0, supports-color@^5.1.0, supports-color@^5.3.0, supports-co dependencies: has-flag "^3.0.0" -supports-color@^6.1.0: +supports-color@^6.0.0, supports-color@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== @@ -23427,7 +24032,7 @@ touch@^2.0.1: dependencies: nopt "~1.0.10" -tough-cookie@^2.3.3, tough-cookie@^2.3.4: +tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== @@ -24446,6 +25051,15 @@ w3c-hr-time@^1.0.1: dependencies: browser-process-hrtime "^0.1.2" +w3c-xmlserializer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.0.1.tgz#054cdcd359dc5d1f3ec9be4e272c756af4b21d39" + integrity sha512-XZGI1OH/OLQr/NaJhhPmzhngwcAnZDLytsvXnRmlYeRkmbb0I7sqFFA22erq4WQR0sUu17ZSQOAV9mFwCqKRNg== + dependencies: + domexception "^1.0.1" + webidl-conversions "^4.0.2" + xml-name-validator "^3.0.0" + wait-for-expect@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-1.1.0.tgz#6607375c3f79d32add35cd2c87ce13f351a3d453" @@ -24544,6 +25158,14 @@ wcwidth@^1.0.0, wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +weak@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/weak/-/weak-1.0.1.tgz#ab99aab30706959aa0200cb8cf545bb9cb33b99e" + integrity sha1-q5mqswcGlZqgIAy4z1RbucszuZ4= + dependencies: + bindings "^1.2.1" + nan "^2.0.5" + web-namespaces@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.2.tgz#c8dc267ab639505276bae19e129dbd6ae72b22b4" @@ -24898,7 +25520,7 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg== -whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== @@ -24920,7 +25542,7 @@ whatwg-fetch@3.0.0, whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0: resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== -whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0: +whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== @@ -25265,6 +25887,13 @@ ws@^6.1.0, ws@~6.1.0: dependencies: async-limiter "~1.0.0" +ws@^6.1.2: + version "6.2.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.0.tgz#13806d9913b2a5f3cbb9ba47b563c002cbc7c526" + integrity sha512-deZYUNlt2O4buFCa3t5bKLf8A7FPP/TVjwOeVNpw818Ma5nk4MLXls2eoEGS39o8119QIYxTrTDoPQ5B/gTD6w== + dependencies: + async-limiter "~1.0.0" + x-is-string@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" @@ -25315,6 +25944,11 @@ xmlbuilder@^9.0.7, xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= +xmlchars@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-1.3.1.tgz#1dda035f833dbb4f86a0c28eaa6ca769214793cf" + integrity sha512-tGkGJkN8XqCod7OT+EvGYK5Z4SfDQGD30zAa58OcnAa0RRWgzUEK72tkXhsX1FZd+rgnhRxFtmO+ihkp8LHSkw== + xmldoc@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/xmldoc/-/xmldoc-0.4.0.tgz#d257224be8393eaacbf837ef227fd8ec25b36888" @@ -25492,7 +26126,7 @@ yargs@^11.0.0: y18n "^3.2.1" yargs-parser "^9.0.2" -yargs@^12.0.1, yargs@^12.0.5: +yargs@^12.0.1, yargs@^12.0.2, yargs@^12.0.5: version "12.0.5" resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==