From 13fc2331361fd53c1cb8f564e82469589423f05b Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 20 Aug 2018 11:32:27 -0500 Subject: [PATCH] Fixes to the wizard --- .../common/test/react_test_helpers.tsx | 124 + .../components/common/tests/unique_id_mock.ts | 16 + .../common/wizard/ManagedWrapper.ts | 46 - .../src/components/common/wizard/RAT/Tab.ts | 87 + .../components/common/wizard/RAT/TabList.ts | 37 + .../components/common/wizard/RAT/TabPanel.ts | 79 + .../common/wizard/RAT/Wrapper.spec.tsx | 524 + .../components/common/wizard/RAT/Wrapper.ts | 57 + .../RAT/__snapshots__/Wrapper.spec.tsx.snap | 10721 ++++++++++++++++ .../RAT/abstract_tabcomponent_state.spec.tsx | 261 + .../wizard/RAT/abstract_tabcomponent_state.ts | 309 + .../common/wizard/RAT/specialAssign.ts | 8 + .../components/common/wizard/Wizard.spec.tsx | 173 +- .../src/components/common/wizard/Wizard.tsx | 14 +- .../common/wizard/WizardPanel.spec.tsx | 8 +- .../components/common/wizard/WizardPanel.tsx | 2 +- .../common/wizard/WizardTab.spec.tsx | 2 +- .../components/common/wizard/WizardTab.tsx | 2 +- .../common/wizard/WizardTabList.tsx | 4 +- .../wizard/__snapshots__/Wizard.spec.tsx.snap | 484 + .../__snapshots__/WizardPanel.spec.tsx.snap | 4 +- .../wizard/abstract_wizard_state.spec.tsx | 253 + .../common/wizard/abstract_wizard_state.ts | 58 + .../src/components/common/wizard/manager.ts | 120 - .../common/wizard/wizard_state.spec.ts | 301 +- .../components/common/wizard/wizard_state.ts | 252 +- .../NonprofitInfoPanel.spec.tsx | 9 +- .../registration_page/RegistrationWizard.tsx | 33 +- .../registration_page/UserInfoPanel.spec.tsx | 11 +- package-lock.json | 5914 ++++----- package.json | 3 +- 31 files changed, 16363 insertions(+), 3553 deletions(-) create mode 100644 javascripts/src/components/common/test/react_test_helpers.tsx create mode 100644 javascripts/src/components/common/tests/unique_id_mock.ts delete mode 100644 javascripts/src/components/common/wizard/ManagedWrapper.ts create mode 100644 javascripts/src/components/common/wizard/RAT/Tab.ts create mode 100644 javascripts/src/components/common/wizard/RAT/TabList.ts create mode 100644 javascripts/src/components/common/wizard/RAT/TabPanel.ts create mode 100644 javascripts/src/components/common/wizard/RAT/Wrapper.spec.tsx create mode 100644 javascripts/src/components/common/wizard/RAT/Wrapper.ts create mode 100644 javascripts/src/components/common/wizard/RAT/__snapshots__/Wrapper.spec.tsx.snap create mode 100644 javascripts/src/components/common/wizard/RAT/abstract_tabcomponent_state.spec.tsx create mode 100644 javascripts/src/components/common/wizard/RAT/abstract_tabcomponent_state.ts create mode 100644 javascripts/src/components/common/wizard/RAT/specialAssign.ts create mode 100644 javascripts/src/components/common/wizard/__snapshots__/Wizard.spec.tsx.snap create mode 100644 javascripts/src/components/common/wizard/abstract_wizard_state.spec.tsx create mode 100644 javascripts/src/components/common/wizard/abstract_wizard_state.ts delete mode 100644 javascripts/src/components/common/wizard/manager.ts diff --git a/javascripts/src/components/common/test/react_test_helpers.tsx b/javascripts/src/components/common/test/react_test_helpers.tsx new file mode 100644 index 00000000..ad6ab99b --- /dev/null +++ b/javascripts/src/components/common/test/react_test_helpers.tsx @@ -0,0 +1,124 @@ +import * as React from 'react' +import {observer} from "mobx-react"; +import * as _ from 'lodash' +import { ReactWrapper, mount } from 'enzyme'; +import { when } from 'mobx'; +import { resolve } from 'path'; +import {mountWithIntl} from "../../../lib/tests/helpers"; + +@observer +class OuterWrapper extends React.Component React.ReactNode }> { + + render() { + const innerProps = _.omit(this.props, '__childrenCreator') as + any + + return this.props.__childrenCreator(innerProps) + } + +} + +/** + * Mobx needs an @observer React component which is created inside the mount tag for enzyme to work. We use this tag to + * create an OuterWrapper component which simply serves this need and passes the given props to + * the given root component. To use, put your props for the root component you're mounting into props. + * + * NOTE: this wraps your root component with an OuterWrapper tag. Can't be avoided. + * @param {TProps} props the properties from the outer scope that your mounted components will need + * @param {(props: TProps) => React.ReactNode} rootComponentCreator a function to create your root component for mounting. + * @returns {ReactWrapper} The wrapper of your mounted component. + */ +export function mountForMobx(props:TProps, + rootComponentCreator:(props:TProps) => React.ReactNode): ReactWrapper { + + + return mount() +} + +/** + * Same as mountForMobx but has support for React-Intl + * @param {TProps} props + * @param {(props: TProps) => React.ReactNode} rootComponentCreator + * @returns {ReactWrapper} + */ +export function mountForMobxWithIntl(props:TProps, + rootComponentCreator:(props:TProps) => React.ReactNode): ReactWrapper { + return mountWithIntl() +} + + +export function waitForMobxCondition( + finishCondition:() => any, + effect: () => any, + whatToDoOnTimeout: () => void = () => expect(false).toBeTruthy() + ){ + when(finishCondition, + () => { + process.nextTick(() => { + effect() + }) + }, + {timeout:10000, onError: whatToDoOnTimeout}); + } + + + + + type TriggerCondition = () => any + + export class TriggerAndAction { + constructor( + readonly finishCondition:TriggerCondition, + readonly action:(done?:Function) => any){} + + async toPromise():Promise { + return new Promise((resolve, reject) => { + waitForMobxCondition( + this.finishCondition, + () => { + this.action() + resolve() + }, + () => { + expect(false).toBeTruthy() + reject() + } + ) + }) + } + + } + + interface TriggerAndActionIface { + finishCondition:TriggerCondition, + action:(done?:Function) => any + } + + +function isFinishAndAction(item: TriggerCondition|TriggerAndActionIface): item is TriggerAndActionIface{ + return !!(item as TriggerAndActionIface).action +} + + export async function runTestsOnConditions(...args:Array) { + let trigAndActions = args.map((i) => { + let ret: TriggerAndAction + if (i instanceof TriggerAndAction) + { + ret = i + } + if (isFinishAndAction(i)) { + ret = new TriggerAndAction(i.finishCondition, i.action) + } + else { + ret = new TriggerAndAction(i, () => {}) + } + + return ret + }) + + trigAndActions.forEach( async (i) =>{ + await i.toPromise() + }) + } \ No newline at end of file diff --git a/javascripts/src/components/common/tests/unique_id_mock.ts b/javascripts/src/components/common/tests/unique_id_mock.ts new file mode 100644 index 00000000..869c3232 --- /dev/null +++ b/javascripts/src/components/common/tests/unique_id_mock.ts @@ -0,0 +1,16 @@ +// Under Lodash license (MIT/ISC) - https://raw.githubusercontent.com/lodash/lodash/4.17.10-npm/LICENSE +import * as _ from 'lodash' + +export class UniqueIdMock { + idCounter:number=0 + + public uniqueId(prefix:string):string { + var id = ++this.idCounter; + return _.toString(prefix) + id; + } + + public reset(){ + this.idCounter = 0 + } + +} \ No newline at end of file diff --git a/javascripts/src/components/common/wizard/ManagedWrapper.ts b/javascripts/src/components/common/wizard/ManagedWrapper.ts deleted file mode 100644 index 4d9e6b9d..00000000 --- a/javascripts/src/components/common/wizard/ManagedWrapper.ts +++ /dev/null @@ -1,46 +0,0 @@ -// License: LGPL-3.0-or-later -import * as React from 'react' -import * as RAT from "react-aria-tabpanel"; -import {TabManager} from "./manager"; -var PropTypes = require('prop-types'); - -var specialAssign = require('react-aria-tabpanel/lib/specialAssign'); - -interface AddManagerInterface { - manager?: TabManager -} - - -var checkedProps = { - children: PropTypes.node.isRequired, - activeTabId: PropTypes.string, - letterNavigation: PropTypes.bool, - onChange: PropTypes.func, - tag: PropTypes.string, -}; - -/** - * Works just like the normal Wrapper but provides a tool for passing in our own TabManager - */ -export class ManagedWrapper extends RAT.Wrapper -{ - manager: TabManager - constructor(props:RAT.WrapperProps & AddManagerInterface){ - super(props) - - if (props.manager) - this.manager = this.props.manager - } - - render() { - var props = this.props; - var elProps = {}; - specialAssign(elProps, props, checkedProps); - return React.createElement(props.tag, elProps, props.children); - } - - - -} - - diff --git a/javascripts/src/components/common/wizard/RAT/Tab.ts b/javascripts/src/components/common/wizard/RAT/Tab.ts new file mode 100644 index 00000000..606dde6c --- /dev/null +++ b/javascripts/src/components/common/wizard/RAT/Tab.ts @@ -0,0 +1,87 @@ +//MIT based on https://github.com/davidtheclark/react-aria-tabpanel/blob/master/lib/Tab.js +import * as React from 'react' +import specialAssign from "./specialAssign"; +import {TabManagerParent} from "./abstract_tabcomponent_state"; +import {observer} from 'mobx-react'; + +const PropTypes = require('prop-types'); + + +const checkedProps = { + children: PropTypes.oneOfType([ + PropTypes.node, + PropTypes.func, + ]).isRequired, + id: PropTypes.string.isRequired, + tag: PropTypes.string, + role: PropTypes.string, + index: PropTypes.number, + active: PropTypes.bool, + letterNavigationText: PropTypes.string, +}; + +interface TabProps { + id: string + active: boolean + letterNavigationText?: string + tag?: string + + [prop: string]: any +} + +@observer +export class Tab extends React.Component { + displayName: 'AriaTabPanel-Tab'; + + public static defaultProps = { tag: 'div', role: 'tab' }; + + static contextTypes = { + atpManager: PropTypes.object.isRequired + }; + + context: {atpManager:TabManagerParent}; + elRef:any; + + handleRef(el:any) { + if (el) { + this.elRef = el; + this.registerWithManager(this.elRef); + } + } + + registerWithManager(elRef:any){ + this.context.atpManager.registerTabElement({ + id: this.props.id, + node: elRef + }); + } + + handleFocus() { + this.context.atpManager.handleTabFocus(this.props.id); + } + + render() { + const props = this.props; + const isActive = props.active; + + const kids = props.children; + + let elProps = { + id: props.id, + tabIndex: (isActive) ? 0 : -1, + onFocus: this.handleFocus.bind(this), + role: props.role, + 'aria-selected': isActive, + 'aria-controls': this.context.atpManager.getTabPanelId(props.id), + ref: this.handleRef.bind(this) + }; + specialAssign(elProps, props, checkedProps); + + return React.createElement(props.tag, elProps, kids); + } + + componentWillUnmount(){ + this.context.atpManager.unregisterTabElement({id:this.props.id}) + } + +} \ No newline at end of file diff --git a/javascripts/src/components/common/wizard/RAT/TabList.ts b/javascripts/src/components/common/wizard/RAT/TabList.ts new file mode 100644 index 00000000..ffe197ca --- /dev/null +++ b/javascripts/src/components/common/wizard/RAT/TabList.ts @@ -0,0 +1,37 @@ +//MIT based on https://github.com/davidtheclark/react-aria-tabpanel/blob/master/lib/Tab.js +import * as React from 'react' +import specialAssign from "./specialAssign"; +import {observer} from 'mobx-react'; + +const PropTypes = require('prop-types'); + +const checkedProps = { + children: PropTypes.node.isRequired, + tag: PropTypes.string, +}; + +interface TabListProps { + tag?: string + + [prop: string]: any +} + +@observer +export class TabList extends React.Component { + displayName: 'AriaTabPanel-TabList'; + + public static defaultProps = {tag: 'div'}; + + static contextTypes = { + atpManager: PropTypes.object.isRequired + }; + + render() { + const props = this.props; + let elProps = { + role: 'tablist', + }; + specialAssign(elProps, props, checkedProps); + return React.createElement(props.tag, elProps, props.children); + } +} \ No newline at end of file diff --git a/javascripts/src/components/common/wizard/RAT/TabPanel.ts b/javascripts/src/components/common/wizard/RAT/TabPanel.ts new file mode 100644 index 00000000..2f7f7453 --- /dev/null +++ b/javascripts/src/components/common/wizard/RAT/TabPanel.ts @@ -0,0 +1,79 @@ +//MIT based on https://github.com/davidtheclark/react-aria-tabpanel/blob/master/lib/Tab.js +import * as React from 'react' +import specialAssign from "./specialAssign"; +import {TabManagerParent} from "./abstract_tabcomponent_state"; +import {observer} from 'mobx-react'; + +const PropTypes = require('prop-types'); + +const checkedProps = { + children: PropTypes.oneOfType([ + PropTypes.node, + PropTypes.func, + ]).isRequired, + tabId: PropTypes.string.isRequired, + tag: PropTypes.string, + active: PropTypes.bool, +}; + +interface TabPanelProps { + tabId: string + active: boolean + tag?: string + + [prop: string]: any +} + +@observer +export class TabPanel extends React.Component { + displayName: 'AriaTabPanel-TabPanel'; + + public static defaultProps = {tag: 'div'}; + + static contextTypes = { + atpManager: PropTypes.object.isRequired + }; + + context: { atpManager: TabManagerParent }; + + handleKeyDown(event: any) { + if (event.ctrlKey && event.key === 'ArrowUp') { + event.preventDefault(); + this.context.atpManager.focusTab(this.props.tabId); + } + } + + registerWithManager(el: any) { + + this.context.atpManager.registerTabPanelElement({ + node: el, + tabId: this.props.tabId, + }); + } + + render() { + const props = this.props; + const isActive = props.active; + + const kids = props.children; + + let style = props.style || {}; + if (!isActive) { + style.display = 'none'; + } + + let elProps = { + className: props.className, + id: this.context.atpManager.getTabPanelId(props.tabId), + onKeyDown: this.handleKeyDown.bind(this), + role: 'tabpanel', + style: style, + 'aria-hidden': !isActive, + 'aria-describedby': props.tabId, + ref: this.registerWithManager.bind(this) + }; + specialAssign(elProps, props, checkedProps); + + return React.createElement(props.tag, elProps, kids); + } +} \ No newline at end of file diff --git a/javascripts/src/components/common/wizard/RAT/Wrapper.spec.tsx b/javascripts/src/components/common/wizard/RAT/Wrapper.spec.tsx new file mode 100644 index 00000000..b21a4815 --- /dev/null +++ b/javascripts/src/components/common/wizard/RAT/Wrapper.spec.tsx @@ -0,0 +1,524 @@ +// License: LGPL-3.0-or-later +import * as React from 'react'; +import 'jest'; +import {action, computed, observable} from 'mobx' +import {AbstractTabComponentState, AbstractTabPanelState} from "./abstract_tabcomponent_state"; +import {mount, ReactWrapper} from 'enzyme'; +import {Wrapper} from "./Wrapper"; +import {TabList} from "./TabList"; +import {TabPanel} from "./TabPanel"; +import {Tab} from "./Tab"; +import toJson from 'enzyme-to-json'; +import {UniqueIdMock} from "../../tests/unique_id_mock"; +import {mountForMobx, runTestsOnConditions, TriggerAndAction} from '../../test/react_test_helpers'; + +let uniqueIdMock = new UniqueIdMock(); + +// function isVisible(wrapper: ReactWrapper) { +// const style: any = wrapper.prop("style"); +// if (style) { +// const display = style['display']; +// if (display) +// return display !== 'none' +// } +// return true +// +// +// } + +// function isEnabledTab(tabWrapper: NonNullable, tabPanelWrapper: NonNullable) { +// expect(tabWrapper).toBeTruthy(); +// expect(tabPanelWrapper).toBeTruthy(); +// expect(tabWrapper.hostNodes().prop('className')).toBe('enabled'); +// expect(isVisible(tabPanelWrapper.hostNodes())).toBeTruthy() +// } + +// function isDisabledTab(tabWrapper: NonNullable, tabPanelWrapper: NonNullable) { +// expect(tabWrapper).toBeTruthy(); +// expect(tabPanelWrapper).toBeTruthy(); +// expect(tabWrapper.prop('className')).toBeUndefined(); +// expect(isVisible(tabPanelWrapper)).toBeFalsy() +// } + +class MockableTabPanelState extends AbstractTabPanelState { + @observable + isEnabled: boolean; + + @action.bound + setEnabled(enabled: boolean) { + this.isEnabled = enabled; + } + + @computed + get enabled(): boolean { + return this.isEnabled + } + +} + + +class EasyTabComponentState extends AbstractTabComponentState { + constructor() { + super(MockableTabPanelState) + } + + wrapperForFocus: ReactWrapper; + + @action.bound + addTab(tab: { tabName: string, label: string }): MockableTabPanelState { + const newTab = super.addTab(tab); + if (this.panels.length == 1) { + + newTab.setEnabled(true); + this.activateTab(newTab) + } + return newTab + } + + uniqueIdFunction(prefix?:string) { + return uniqueIdMock.uniqueId.bind(uniqueIdMock)(prefix); + } + + focusFunction(panel:MockableTabPanelState){ + this.wrapperForFocus.find(`#${panel.id}`).hostNodes().prop('onFocus')(null) + } + +} + +describe('Wrapper', () => { + + let data = + { + tab1: { + tabName: "Tab1", + label: "Label1" + }, + tab2: { + tabName: "Tab2", + label: "Label2" + }, + tab3: { + tabName: "Tab3", + label: "Label3" + }, + + + }; + + beforeEach(() => { + uniqueIdMock.reset() + }); + + it('.createTab', () => { + let state = new EasyTabComponentState(); + + + state.addTab({tabName: data.tab1.tabName, label: data.tab1.label}); + let tab = state.tabsByName[data.tab1.tabName]; + + let wrapper = mount( + + {tab.label} + +
+ tabPanel1 +
+
); + + let tab1 = wrapper.find(`#${tab.id}`); + let hostNode = tab1.hostNodes().at(0); + + + hostNode.simulate('focus'); + wrapper.update(); + + + expect(toJson(wrapper)).toMatchSnapshot() + }); + + describe('prevents going to next if next isnt enabled', () => { + let state: EasyTabComponentState; + let wrapper: ReactWrapper; + + beforeEach(() => { + state = new EasyTabComponentState(); + state.addTab({tabName: data.tab1.tabName, label: data.tab1.label}); + state.addTab({tabName: data.tab2.tabName, label: data.tab2.label}); + + + wrapper = mountForMobx({state: state}, (props) => { + return + + { + props.state.panels.map((tab) => { + return {tab.label} + }) + } + +
+ { + props.state.panels.map((tab) => { + return +
+
+ }); + + state.wrapperForFocus = wrapper + + }); + + + it('ignores if focused', () => { + let secondTab = wrapper.find(Tab).at(1); + secondTab.simulate('focus'); + wrapper.update(); + expect(toJson(wrapper)).toMatchSnapshot() + }); + + it('ignores from next button', () => { + let button = wrapper.find('button').at(0); + button.simulate('click'); + wrapper.update(); + expect(toJson(wrapper)).toMatchSnapshot() + }); + + it('ignores from backend move attempt', () => { + + state.moveToTab(state.tabsByName[data.tab2.tabName].id); + wrapper.update(); + expect(toJson(wrapper)).toMatchSnapshot() + }) + }); + + describe('go to next and back', () => { + let wrapper: ReactWrapper; + let state: EasyTabComponentState; + beforeEach(() => { + state = new EasyTabComponentState(); + state.addTab({tabName: data.tab1.tabName, label: data.tab1.label}); + state.addTab({tabName: data.tab2.tabName, label: data.tab2.label}); + state.addTab({tabName: data.tab3.tabName, label: data.tab3.label}); + + let tab1 = state.tabsByName[data.tab1.tabName]; + tab1.setEnabled(true); + let tab2 = (state.tabsByName[data.tab2.tabName] as MockableTabPanelState); + tab2.setEnabled(true); + let tab3 = state.tabsByName[data.tab3.tabName]; + tab3.setEnabled(true); + + + wrapper = mountForMobx({state: state}, (props) => { + + return + + { + props.state.panels.map((tab) => { + return {tab.label} + }) + } + +
+ { + props.state.panels.map((tab) => { + return +
+
+ + }); + + state.wrapperForFocus = wrapper + }); + + + describe('go to next', () => { + let commonCondition: (done: Function) => any; + + beforeEach(() => { + commonCondition = (done: Function) => { + + runTestsOnConditions(new TriggerAndAction( + () =>state.activeTab.tabName == data.tab2.tabName, + () => { + wrapper.instance().forceUpdate(); + wrapper.update(); + expect(toJson(wrapper)).toMatchSnapshot(); + done() + } + )) + }; + }); + + + it('go to next via click', (done) => { + commonCondition(done); + let secondTab = wrapper.find('#tab2'); + + secondTab.hostNodes().simulate('focus'); + + }); + + it('go to next via next button', (done) => { + commonCondition(done); + let button = wrapper.find('button').at(0); + button.hostNodes().simulate('click'); + + }); + + it('go to next via backend', (done) => { + commonCondition(done); + state.moveToTab(state.tabsByName[data.tab2.tabName].id); + + }) + }); + + describe('go to next twice', () => { + let commonCondition: (done: Function) => any; + + beforeEach(() => { + commonCondition = (done: Function) => { + + runTestsOnConditions(new TriggerAndAction( + () =>state.activeTab.tabName == data.tab3.tabName, + () => { + wrapper.instance().forceUpdate(); + wrapper.update(); + expect(toJson(wrapper)).toMatchSnapshot(); + done() + } + )) + }; + + }); + + it('go to next via click', (done) => { + commonCondition(done); + let secondTab = wrapper.find(Tab).at(1); + secondTab.simulate('focus'); + wrapper.update(); + let thirdTab = wrapper.find(Tab).at(2); + thirdTab.simulate('focus'); + }); + + it('go to next via next button', (done) => { + commonCondition(done); + let button = wrapper.find('button').at(0); + button.prop('onClick')(null); + + button = wrapper.find('button').at(1); + button.prop('onClick')(null); + + + + }); + + it('go to next via backend', (done) => { + commonCondition(done); + state.moveToNextTab(); + state.moveToNextTab(); + + }) + }); + + describe('go next twice, then back once', () => { + + let commonCondition: (done: Function) => void; + beforeEach(() => { + commonCondition = (done: Function) => { + + runTestsOnConditions(new TriggerAndAction( + () =>state.activeTab.tabName == data.tab3.tabName, + () => { + wrapper.instance().forceUpdate(); + wrapper.update(); + } + ), + new TriggerAndAction( + () => state.activeTab.tabName == data.tab2.tabName, + () => { + wrapper.instance().forceUpdate(); + wrapper.update(); + expect(toJson(wrapper)).toMatchSnapshot(); + done() + } + )) + }; + }); + + it('go via tab presses', (done) => { + + commonCondition(done); + let secondTab = wrapper.find(Tab).at(1); + secondTab.simulate('focus'); + + let thirdTab = wrapper.find(Tab).at(2); + thirdTab.simulate('focus'); + + + secondTab.simulate('focus') + }); + + it('go via next button presses', (done) => { + commonCondition(done); + let button = wrapper.find('button').at(0); + button.prop('onClick')(null); + + button = wrapper.find('button').at(1); + button.prop('onClick')(null); + let secondTab = wrapper.find(Tab).at(1); + secondTab.simulate('focus') + + }); + + it('go via backend', (done) => { + commonCondition(done); + state.moveToNextTab(); + state.moveToNextTab(); + state.moveToPreviousTab() + + }) + }) + }); + + + describe('handle enabled properly', () => { + + let wrapper: ReactWrapper; + let state: EasyTabComponentState; + let tab3: MockableTabPanelState; + let tab2: MockableTabPanelState; + beforeEach(() => { + state = new EasyTabComponentState(); + state.addTab({tabName: data.tab1.tabName, label: data.tab1.label}); + state.addTab({tabName: data.tab2.tabName, label: data.tab2.label}); + state.addTab({tabName: data.tab3.tabName, label: data.tab3.label}); + + let tab1 = state.tabsByName[data.tab1.tabName]; + tab1.setEnabled(true); + tab2 = (state.tabsByName[data.tab2.tabName] as MockableTabPanelState); + tab2.setEnabled(true); + tab3 = state.tabsByName[data.tab3.tabName]; + tab3.setEnabled(true); + + + wrapper = mountForMobx({state: state}, (props) => { + + return + + { + props.state.panels.map((tab) => { + return {tab.label} + }) + } + +
+ { + props.state.panels.map((tab) => { + return +
+
+ + }); + + state.wrapperForFocus = wrapper + }); + + it('move back to previous tab if the current one is disabled', (done) => { + + runTestsOnConditions(new TriggerAndAction( + () =>state.activeTab.tabName == data.tab3.tabName, + () => { + wrapper.instance().forceUpdate(); + wrapper.update(); + } + ), + new TriggerAndAction( + () => state.activeTab.tabName == data.tab2.tabName, + () => { + wrapper.instance().forceUpdate(); + wrapper.update(); + + + } + ), + new TriggerAndAction( + () => tab3.enabled, + () => { + wrapper.instance().forceUpdate(); + wrapper.update(); + + expect(toJson(wrapper)).toMatchSnapshot(); + done() + } + )); + + tab3.setEnabled(true); + tab2.setEnabled(true); + state.moveToTab(tab3); + tab3.setEnabled(false); + + + + + }); + + it('move back to first tab if all but that one is disabled', (done) => { + runTestsOnConditions(new TriggerAndAction( + () =>state.activeTab.tabName == data.tab3.tabName, + () => { + wrapper.instance().forceUpdate(); + wrapper.update(); + } + ), + new TriggerAndAction( + () => state.activeTab.tabName == data.tab1.tabName, + () => { + wrapper.instance().forceUpdate(); + wrapper.update(); + + + } + ), + new TriggerAndAction( + () => tab2.enabled, + () => { + wrapper.instance().forceUpdate(); + wrapper.update(); + + expect(toJson(wrapper)).toMatchSnapshot(); + done(); + } + )); + + state.moveToTab(tab3); + + expect(tab3.active).toBe(true); + + tab2.setEnabled(false); + tab3.setEnabled(false); + state.moveToNextTab(); + + tab2.setEnabled(true); + + + }) + + }); + +}); + diff --git a/javascripts/src/components/common/wizard/RAT/Wrapper.ts b/javascripts/src/components/common/wizard/RAT/Wrapper.ts new file mode 100644 index 00000000..6e5e1feb --- /dev/null +++ b/javascripts/src/components/common/wizard/RAT/Wrapper.ts @@ -0,0 +1,57 @@ +// License: LGPL-3.0-or-later +import * as React from 'react' +import {ReactNode} from 'react' +import {TabManagerParent} from "./abstract_tabcomponent_state"; +import {observer} from 'mobx-react'; + +const PropTypes = require('prop-types'); +const specialAssign = require('react-aria-tabpanel/lib/specialAssign'); + +interface WrapperProps { + manager: TabManagerParent + tag?: string + children: ReactNode + [props:string]: any +} + +const checkedProps = { + children: PropTypes.node.isRequired, + tag: PropTypes.string, + manager: PropTypes.object.isRequired +}; + +/** + * Works just like the normal Wrapper but supports our own tab manager + */ +@observer +export class Wrapper extends React.Component { + + displayName = 'AriaTabPanel-Wrapper'; + + public static defaultProps = { + tag: 'div' + }; + + public static childContextTypes = { + atpManager: PropTypes.object.isRequired, + }; + + getChildContext() { + return {atpManager: this.props.manager}; + } + + componentWillUnmount() { + this.props.manager.destroy(); + } + + componentDidMount() { + this.props.manager.activate(); + } + + render() { + const props = this.props; + let elProps = {}; + specialAssign(elProps, props, checkedProps); + return React.createElement(props.tag, elProps, props.children); + } +} diff --git a/javascripts/src/components/common/wizard/RAT/__snapshots__/Wrapper.spec.tsx.snap b/javascripts/src/components/common/wizard/RAT/__snapshots__/Wrapper.spec.tsx.snap new file mode 100644 index 00000000..d53161ad --- /dev/null +++ b/javascripts/src/components/common/wizard/RAT/__snapshots__/Wrapper.spec.tsx.snap @@ -0,0 +1,10721 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Wrapper .createTab 1`] = ` + + Label1 + , + "parent": [Circular], + "tabName": "Tab1", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + ], + } + } + tag="div" +> +
+ +
+ + + +
+
+
+ +
+ tabPanel1 +
+
+
+
+
+`; + +exports[`Wrapper go to next and back go next twice, then back once go via backend 1`] = ` + + Label2 + , + "parent": [Circular], + "tabName": "Tab2", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } +> + + Label2 + , + "parent": [Circular], + "tabName": "Tab2", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } + tag="div" + > +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; + +exports[`Wrapper go to next and back go next twice, then back once go via next button presses 1`] = ` + + Label2 + , + "parent": [Circular], + "tabName": "Tab2", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } +> + + Label2 + , + "parent": [Circular], + "tabName": "Tab2", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } + tag="div" + > +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; + +exports[`Wrapper go to next and back go next twice, then back once go via tab presses 1`] = ` + + Label2 + , + "parent": [Circular], + "tabName": "Tab2", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } +> + + Label2 + , + "parent": [Circular], + "tabName": "Tab2", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } + tag="div" + > +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; + +exports[`Wrapper go to next and back go to next go to next via backend 1`] = ` + + Label2 + , + "parent": [Circular], + "tabName": "Tab2", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } +> + + Label2 + , + "parent": [Circular], + "tabName": "Tab2", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } + tag="div" + > +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; + +exports[`Wrapper go to next and back go to next go to next via click 1`] = ` + + Label2 + , + "parent": [Circular], + "tabName": "Tab2", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } +> + + Label2 + , + "parent": [Circular], + "tabName": "Tab2", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } + tag="div" + > +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; + +exports[`Wrapper go to next and back go to next go to next via next button 1`] = ` + + Label2 + , + "parent": [Circular], + "tabName": "Tab2", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } +> + + Label2 + , + "parent": [Circular], + "tabName": "Tab2", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } + tag="div" + > +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; + +exports[`Wrapper go to next and back go to next twice go to next via backend 1`] = ` + + Label3 + , + "parent": [Circular], + "tabName": "Tab3", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } +> + + Label3 + , + "parent": [Circular], + "tabName": "Tab3", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } + tag="div" + > +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; + +exports[`Wrapper go to next and back go to next twice go to next via click 1`] = ` + + Label3 + , + "parent": [Circular], + "tabName": "Tab3", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } +> + + Label3 + , + "parent": [Circular], + "tabName": "Tab3", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } + tag="div" + > +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; + +exports[`Wrapper go to next and back go to next twice go to next via next button 1`] = ` + + Label3 + , + "parent": [Circular], + "tabName": "Tab3", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } +> + + Label3 + , + "parent": [Circular], + "tabName": "Tab3", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": true, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } + tag="div" + > +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; + +exports[`Wrapper handle enabled properly move back to first tab if all but that one is disabled 1`] = ` + + Label1 + , + "parent": [Circular], + "tabName": "Tab1", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": false, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } +> + + Label1 + , + "parent": [Circular], + "tabName": "Tab1", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": false, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } + tag="div" + > +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; + +exports[`Wrapper handle enabled properly move back to previous tab if the current one is disabled 1`] = ` + + Label2 + , + "parent": [Circular], + "tabName": "Tab2", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": false, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } +> + + Label2 + , + "parent": [Circular], + "tabName": "Tab2", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label1", + }, + Object { + "node": , + "text": "label3", + }, + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": true, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + MockableTabPanelState { + "id": "tab3", + "isEnabled": false, + "label": "Label3", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab3", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } + tag="div" + > +
+ +
+ + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; + +exports[`Wrapper prevents going to next if next isnt enabled ignores from backend move attempt 1`] = ` + + Label1 + , + "parent": [Circular], + "tabName": "Tab1", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": undefined, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } +> + + Label1 + , + "parent": [Circular], + "tabName": "Tab1", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": undefined, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } + tag="div" + > +
+ +
+ + + + + + +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; + +exports[`Wrapper prevents going to next if next isnt enabled ignores from next button 1`] = ` + + Label1 + , + "parent": [Circular], + "tabName": "Tab1", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": undefined, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } +> + + Label1 + , + "parent": [Circular], + "tabName": "Tab1", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": undefined, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } + tag="div" + > +
+ +
+ + + + + + +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; + +exports[`Wrapper prevents going to next if next isnt enabled ignores if focused 1`] = ` + + Label1 + , + "parent": [Circular], + "tabName": "Tab1", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": undefined, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } +> + + Label1 + , + "parent": [Circular], + "tabName": "Tab1", + }, + "focusGroup": FocusGroup { + "_boundHandleKeydownEvent": [Function], + "_members": Array [ + Object { + "node": , + "text": "label2", + }, + Object { + "node": , + "text": "label1", + }, + ], + "_searchString": "", + "_settings": Object { + "backArrows": Array [ + "up", + "left", + ], + "forwardArrows": Array [ + "down", + "right", + ], + "stringSearch": true, + "stringSearchDelay": 800, + "wrap": true, + }, + }, + "panelType": [Function], + "panels": Array [ + MockableTabPanelState { + "id": "tab1", + "isEnabled": true, + "label": "Label1", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab1", + }, + MockableTabPanelState { + "id": "tab2", + "isEnabled": undefined, + "label": "Label2", + "letterNavigationText": undefined, + "node": , + "parent": [Circular], + "tabName": "Tab2", + }, + ], + "wrapperForFocus": + +
+ +
+ + + + + + +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
, + } + } + tag="div" + > +
+ +
+ + + + + + +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; diff --git a/javascripts/src/components/common/wizard/RAT/abstract_tabcomponent_state.spec.tsx b/javascripts/src/components/common/wizard/RAT/abstract_tabcomponent_state.spec.tsx new file mode 100644 index 00000000..9dd9dc82 --- /dev/null +++ b/javascripts/src/components/common/wizard/RAT/abstract_tabcomponent_state.spec.tsx @@ -0,0 +1,261 @@ +// License: LGPL-3.0-or-later +import * as React from 'react'; +import 'jest'; +import {observable, action, computed} from 'mobx' +import {AbstractTabComponentState, AbstractTabPanelState} from "./abstract_tabcomponent_state"; + +class MockableTabPanelState extends AbstractTabPanelState { + @observable + isEnabled:boolean; + + @action.bound + setEnabled(enabled:boolean){ + this.isEnabled = enabled; + } + + @computed + get enabled():boolean { + return this.isEnabled + } + +} + +class EasyTabComponentState extends AbstractTabComponentState { + constructor() { + super(MockableTabPanelState) + } + + @action.bound + addTab(tab:{ tabName: string, label: string }):MockableTabPanelState { + const newTab = super.addTab(tab); + if (this.panels.length == 1){ + newTab.setEnabled(true) + } + return newTab + } + + //we mock this because we want to skip the focus group code for these tests + @action.bound + focusTab(id:string) : void { + this.activateTab(id) + } +} + + +describe('AbstractTabComponentState', () => { + + let data = + { + tab1: { + tabName: "Tab1", + label: "Label1" + }, + tab2: { + tabName: "Tab2", + label: "Label2" + }, + tab3: { + tabName: "Tab3", + label: "Label3" + }, + + + }; + it('.createTab', () =>{ + let state = new EasyTabComponentState(); + + + state.addTab({tabName:data.tab1.tabName, label:data.tab1.label}); + + + let tab = state.tabsByName[data.tab1.tabName]; + expect(tab.tabName).toBe(data.tab1.tabName); + expect(tab.label).toBe(data.tab1.label); + expect(tab.enabled).toBe(true); + expect(tab.previous).toBe(null); + expect(tab.next).toBe(null) + }); + + it('prevents going to next if next isnt enabled', () =>{ + let state = new EasyTabComponentState(); + + state.addTab({tabName:data.tab1.tabName, label:data.tab1.label}); + state.addTab({tabName:data.tab2.tabName, label:data.tab2.label}); + + + expect(state.activeTab).toBe(state.tabsByName[data.tab1.tabName]); + state.moveToTab(state.tabsByName[data.tab2.tabName].id); + expect(state.activeTab).toBe(state.tabsByName[data.tab1.tabName]); + expect(state.tabsByName[data.tab2.tabName].active).toBeFalsy() + }); + + describe('go to next and back', () => { + let state = new EasyTabComponentState(); + + state.addTab({tabName:data.tab1.tabName, label:data.tab1.label}); + state.addTab({tabName:data.tab2.tabName, label:data.tab2.label}); + state.addTab({tabName:data.tab3.tabName, label:data.tab3.label}); + + let tab1 = state.tabsByName[data.tab1.tabName]; + tab1.setEnabled(true); + let tab2 = (state.tabsByName[data.tab2.tabName] as MockableTabPanelState); + tab2.setEnabled(true); + let tab3 = state.tabsByName[data.tab3.tabName]; + it('go to next', () =>{ + expect(state.nextTab).toBe(tab2); + expect(state.previousTab).toBeNull(); + + state.moveToNextTab(); + + expect(state.activeTab).toBe(tab2); + + expect(state.previousTab).toBe(tab1); + expect(state.nextTab).toBe(tab3); + + expect(tab1.active).toBeFalsy(); + expect(tab3.active).toBeFalsy() + + }) + }); + + + describe('handle moving back to tabs when one is disabled', () => { + + let state:EasyTabComponentState= null; + let tab1: MockableTabPanelState, tab2: MockableTabPanelState, tab3 : MockableTabPanelState = null; + + beforeEach(() => { + state = new EasyTabComponentState(); + state.addTab({tabName:data.tab1.tabName, label:data.tab1.label}); + state.addTab({tabName:data.tab2.tabName, label:data.tab2.label}); + state.addTab({tabName:data.tab3.tabName, label:data.tab3.label}); + + tab1 = (state.tabsByName[data.tab1.tabName] as MockableTabPanelState); + tab1.setEnabled(true); + tab2 = (state.tabsByName[data.tab2.tabName] as MockableTabPanelState); + tab2.setEnabled(true); + tab3 = (state.tabsByName[data.tab3.tabName]as MockableTabPanelState) + }); + + it ('move back to previous tab if the current one is disabled', () =>{ + + tab3.setEnabled(true); + tab2.setEnabled(true); + state.moveToTab(tab3); + + expect(tab3.active).toBe(true); + + expect(tab1.active).toBe(false); + expect(tab2.active).toBe(false); + tab3.setEnabled(false); + + expect(tab3.active).toBe(false); + expect(tab2.active).toBe(true); + expect(state.activeTab).toBe(tab2) + + + }); + + it ('move back to first tab if all but that one is disabled', () =>{ + tab2.setEnabled(true); + tab3.setEnabled(true); + state.moveToTab(tab3); + + expect(tab3.active).toBe(true); + + tab2.setEnabled(false); + tab3.setEnabled(false); + + + expect(tab3.active).toBe(false); + expect(tab2.active).toBe(false); + expect(tab1.active).toBe(true); + expect(state.activeTab).toBe(tab1); + + + tab1.setEnabled(true); + expect(state.activeTab).toBe(tab1); + + + tab1.setEnabled(false); + expect(state.activeTab).toBe(tab1); + + + state.moveToNextTab(); + expect(state.activeTab).toBe(tab1); + + + tab2.setEnabled(true); + expect(state.activeTab).toBe(tab1) + + }) + + }); + + + + describe("before works properly", () =>{ + let state:EasyTabComponentState = null; + let tab1: MockableTabPanelState, tab2: MockableTabPanelState, tab3 : MockableTabPanelState = null; + beforeEach(() => { + state = new EasyTabComponentState(); + state.addTab({tabName:data.tab1.tabName, label:data.tab1.label}); + + tab1 = (state.tabsByName[data.tab1.tabName] as MockableTabPanelState) + }); + + it("handles before when nothing else in it",() =>{ + + let tab2 = new MockableTabPanelState(); + + expect(tab1.before(tab2)).toBeFalsy() + }); + + it("handles before before When nothing else in it",() =>{ + + state.addTab({tabName:data.tab2.tabName, label:data.tab2.label}) + ; + tab2 = (state.tabsByName[data.tab2.tabName] as MockableTabPanelState); + expect(tab1.before(tab2)).toBeTruthy(); + expect(tab2.before(tab1)).toBeFalsy(); + tab3 = new MockableTabPanelState(); + + expect(tab2.before(tab3)).toBeFalsy(); + expect(tab3.before(tab2)).toBeFalsy() + }) + + }); + + describe("after works properly", () =>{ + let state:EasyTabComponentState = null; + let tab1: MockableTabPanelState, tab2: MockableTabPanelState, tab3 : MockableTabPanelState = null; + beforeEach(() => { + state = new EasyTabComponentState(); + state.addTab({tabName:data.tab1.tabName, label:data.tab1.label}); + + tab1 = (state.tabsByName[data.tab1.tabName] as MockableTabPanelState) + }); + + it("handles before when nothing else in it",() =>{ + + let tab2 = new MockableTabPanelState(); + + expect(tab1.after(tab2)).toBeFalsy(); + expect(tab2.after(tab1)).toBeFalsy() + }); + + it("handles before before When nothing else in it",() =>{ + + state.addTab({tabName:data.tab2.tabName, label:data.tab2.label}); + tab2 = (state.tabsByName[data.tab2.tabName] as MockableTabPanelState); + expect(tab2.after(tab1)).toBeTruthy(); + expect(tab1.after(tab2)).toBeFalsy(); + tab3 = new MockableTabPanelState(); + + expect(tab2.before(tab3)).toBeFalsy(); + expect(tab3.before(tab2)).toBeFalsy() + }) + + }) + +}); diff --git a/javascripts/src/components/common/wizard/RAT/abstract_tabcomponent_state.ts b/javascripts/src/components/common/wizard/RAT/abstract_tabcomponent_state.ts new file mode 100644 index 00000000..d55d3b89 --- /dev/null +++ b/javascripts/src/components/common/wizard/RAT/abstract_tabcomponent_state.ts @@ -0,0 +1,309 @@ +// License: LGPL-3.0-or-later +import {action, computed, observable, reaction, runInAction} from "mobx"; +import _ = require("lodash"); + +const createFocusGroup = require('focus-group'); + +export interface TabManagerParent { + registerTabElement(tab: { id: string, node: any}): void + + registerTabPanelElement(tabPanel: { tabId: string, node: any }): void + + unregisterTabElement(tab: { id: string }): void + + unregisterTabPanelElement(tabPanel: { id: string }): void + + handleTabFocus(tabId: string): void + + getTabPanelId(id: string): string + + focusTab(id: string): void + + activate(): void + + destroy(): void +} + + +export abstract class AbstractTabComponentState + implements TabManagerParent { + + @observable focusGroup: any; + @observable panels:Array = []; + @observable activeTab: PanelStateType; + + + protected constructor(readonly panelType: { new(): PanelStateType }) { + + const focusGroupOptions = { + wrap: true, + forwardArrows: ['down', 'right'], + backArrows: ['up', 'left'], + stringSearch: true, + }; + + this.focusGroup = createFocusGroup(focusGroupOptions); + + reaction(() => this.activeTab && this.activeTab.enabled , () => { + if (!this.activeTab.enabled){ + this.strategy(this) + } + + }) + } + + @computed + get tabsByName(): { [name: string]: PanelStateType } { + return _.fromPairs(this.panels.map((i) => [i.tabName, i])); + } + + @computed + get nextTab(): PanelStateType { + return this.activeTab.next + } + + @computed + get previousTab(): PanelStateType { + return this.activeTab.previous + } + + + addTab(tab:{ tabName: string, label: string }): PanelStateType { + let newTab:PanelStateType; + runInAction(() => { + newTab = this.createChildState(); + newTab.id = this.uniqueIdFunction('tab'); + newTab.tabName = tab.tabName; + newTab.label = tab.label; + if (this.panels.length == 0) { + this.activeTab = newTab + } + newTab.parent = this; + + this.panels.push(newTab) + }); + return newTab; + } + + moveToTab(tab: PanelStateType | string) { + + let tabState: PanelStateType = null; + if (tab instanceof AbstractTabPanelState) { + tabState = tab + } + else { + tabState = _.find(this.panels, (i) => i.id == tab) + } + + this.focusTab(tabState.id) + } + + @action.bound + activateTab(tab: PanelStateType | string) { + + let tabState: PanelStateType = null; + if (tab instanceof AbstractTabPanelState) { + tabState = tab + } + else { + tabState = _.find(this.panels, (i) => i.id == tab) + } + + if (this.canChangeTo(tabState.id)) { + this.activeTab = tabState + } + } + + @action.bound + moveToNextTab() { + const self = this; + + if (this.nextTab) { + self.focusTab(this.nextTab.id) + } + } + + + @action.bound + moveToPreviousTab() { + const self = this; + + if (this.previousTab) { + self.focusTab(this.previousTab.id) + } + } + + @action.bound + canChangeTo(tabId: string): boolean { + + const tab = _.find(this.panels, (i) => i.id == tabId); + return tab && tab.enabled + } + + focusTab(id: string): void { + runInAction(() => { + let tabMemberToFocus = _.find(this.panels, (panel) => panel.id === id); + if (!tabMemberToFocus) return; + this.focusFunction(tabMemberToFocus) + }) + } + + getTabPanelId(id: string): string { + return id + '-panel'; + } + + @action.bound + handleTabFocus(tabId: string): void { + this.activateTab(tabId); + } + + @action.bound + unregisterTabPanelElement(tabPanel: { id: string; }): void { + + } + + @action.bound + unregisterTabElement(tab: { id: string; }): void { + //throw new Error("Method not implemented."); + } + + @action.bound + registerTabPanelElement(tabPanel: { tabId: string; node: any; }): void { + + } + + @action.bound + registerTabElement(tabMember: { id: string; node: any }): void { + let tabMemberToRegister = _.find(this.panels, (panel) => panel.id === tabMember.id); + let focusGroupMember = (tabMemberToRegister.letterNavigationText) ? { + node: tabMember.node, + text: tabMemberToRegister.letterNavigationText, + } : tabMember.node; + tabMemberToRegister.node = tabMember.node; + this.focusGroup.addMember(focusGroupMember, tabMember); + } + + @action.bound + activate() { + this.focusGroup.activate(); + } + + @action.bound + destroy() { + this.focusGroup.destroy(); + } + + @action.bound + protected createChildState(): PanelStateType { + return new this.panelType() + } + + /** + * TESTING ONLY: The function used to focus on a particular tab. We override in Enzyme tests + * @param {PanelStateType} panel + */ + protected focusFunction(panel:PanelStateType) { + panel.node.focus() + } + + /** + * TESTING ONLY: The function used for getting unique id. We override in tests to get consistent ids. + * @param {string} prefix + * @returns {string} + */ + protected uniqueIdFunction(prefix?:string):string { + return _.uniqueId(prefix) + } + + protected strategy(state:this) { + let testTab = state.activeTab ? state.activeTab.previous : null; + while (testTab) + { + if (testTab.enabled) + { + state.activeTab = testTab; + return + } + testTab = testTab.previous + } + state.activeTab = _.first(state.panels) + }; +} + +export abstract class AbstractTabPanelState { + @observable parent: AbstractTabComponentState; + @observable id: string; + @observable tabName: string; + @observable label: string; + + @observable letterNavigationText: string; + + @observable node: any; + + abstract get enabled(): boolean + + @computed + get active(): boolean { + return this.parent.activeTab === this + } + + @computed + get previous(): this { + if (!this.parent || !this.parent.panels) + return null; + const index = _.findIndex(this.parent.panels, (i) => i == this); + if (index === null) { + // return null but we have a problem here + return null + } + if (index === 0) { + // there is no previous one because we're first! + return null; + } + + return this.parent.panels[index - 1] as this + } + + @computed + get next(): this { + if (!this.parent || !this.parent.panels) + return null; + + const index = _.findIndex(this.parent.panels, (i) => i == this); + const panelLength = this.parent.panels.length; + if (index === null) { + // return null but we have a problem here + return null; + } + + if (index + 1 >= panelLength) { + //we have no advanced + return null; + } + + return this.parent.panels[index + 1] as this + } + + before(tab: this): boolean { + let testItem: this = this; + while (testItem.next != tab) { + if (!testItem.next) + return false; + testItem = testItem.next + } + + return true + } + + + after(tab: this): boolean { + let testItem: this = this; + while (testItem.previous != tab) { + if (!testItem.previous) + return false; + testItem = testItem.previous; + } + + return true + } +} \ No newline at end of file diff --git a/javascripts/src/components/common/wizard/RAT/specialAssign.ts b/javascripts/src/components/common/wizard/RAT/specialAssign.ts new file mode 100644 index 00000000..5a7f4b05 --- /dev/null +++ b/javascripts/src/components/common/wizard/RAT/specialAssign.ts @@ -0,0 +1,8 @@ +export default function specialAssign(a:any, b:any, reserved:any) { + for (var x in b) { + if (!b.hasOwnProperty(x)) continue; + if (a[x]) continue; + if (reserved[x]) continue; + a[x] = b[x]; + } +} \ No newline at end of file diff --git a/javascripts/src/components/common/wizard/Wizard.spec.tsx b/javascripts/src/components/common/wizard/Wizard.spec.tsx index 6b37a260..ad8b456a 100644 --- a/javascripts/src/components/common/wizard/Wizard.spec.tsx +++ b/javascripts/src/components/common/wizard/Wizard.spec.tsx @@ -1,15 +1,17 @@ // License: LGPL-3.0-or-later import * as React from 'react'; import 'jest'; -import * as Component from './Wizard' +import {Wizard} from './Wizard' import {WizardState, WizardTabPanelState} from "./wizard_state"; import {Form} from "mobx-react-form"; -import {computed, observable, action} from 'mobx'; -import {Wizard} from "./Wizard"; -import {shallow} from 'enzyme'; +import {action, computed, observable} from 'mobx'; +import {ReactWrapper} from 'enzyme'; import {WizardPanel} from "./WizardPanel"; -import toJson from 'enzyme-to-json'; +import {mountForMobxWithIntl, runTestsOnConditions, TriggerAndAction} from "../test/react_test_helpers"; +import {UniqueIdMock} from "../tests/unique_id_mock"; + +let uniqueIdMock = new UniqueIdMock(); class MockableTabPanelState extends WizardTabPanelState { @observable @@ -35,6 +37,16 @@ class EasyWizardState extends WizardState{ return new Form(i) } + uniqueIdFunction(prefix?:string) { + return uniqueIdMock.uniqueId.bind(uniqueIdMock)(prefix); + } + + focusFunction(panel:MockableTabPanelState){ + this.wrapperForFocus.find(`#${panel.id}`).hostNodes().prop('onFocus')(null) + } + + wrapperForFocus: ReactWrapper; + } describe('Wizard', () => { @@ -59,53 +71,146 @@ describe('Wizard', () => { } + beforeEach(() => { + uniqueIdMock.reset() + }); + + + let state:EasyWizardState = null let tab1: MockableTabPanelState, tab2: MockableTabPanelState, tab3 : MockableTabPanelState = null + let wrapper: ReactWrapper; + let disabledTabs: boolean = false beforeEach(() => { state = new EasyWizardState() - state.addTab(data.tab1.tabName, data.tab1.label, data.tab1.subFormDef) - state.addTab(data.tab2.tabName, data.tab2.label, data.tab2.subFormDef) - state.addTab(data.tab3.tabName, data.tab3.label, data.tab3.subFormDef) + state.addTab({tabName: data.tab1.tabName, label: data.tab1.label, tabFieldDefinition: data.tab1.subFormDef}); + state.addTab({tabName: data.tab2.tabName, label: data.tab2.label, tabFieldDefinition: data.tab2.subFormDef}); + state.addTab({tabName: data.tab3.tabName, label: data.tab3.label, tabFieldDefinition: data.tab3.subFormDef}); state.initialize() tab1 = (state.tabsByName[data.tab1.tabName] as MockableTabPanelState) tab1.setValid(true) tab2 = (state.tabsByName[data.tab2.tabName] as MockableTabPanelState) tab2.setValid(true) tab3 = (state.tabsByName[data.tab3.tabName] as MockableTabPanelState) + wrapper = mountForMobxWithIntl({state:state, disabledTabs: disabledTabs}, (props) => { + return + { + props.state.panels.map((tab) => { + return +