blob: ff5a5583be45b3000d0d235a5a90ce2b698b14dc [file] [log] [blame]
// Copyright (C) 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {Child, Controller} from './controller';
const _onCreate = jest.fn();
const _onDestroy = jest.fn();
const _run = jest.fn();
type MockStates = 'idle' | 'state1' | 'state2' | 'state3';
class MockController extends Controller<MockStates> {
constructor(public type: string) {
super('idle');
_onCreate(this.type);
}
run() {
return _run(this.type);
}
onDestroy() {
return _onDestroy(this.type);
}
}
function runControllerTree(rootController: MockController): void {
for (let runAgain = true, i = 0; runAgain; i++) {
if (i >= 100) throw new Error('Controller livelock');
runAgain = rootController.invoke();
}
}
beforeEach(() => {
_onCreate.mockClear();
_onCreate.mockReset();
_onDestroy.mockClear();
_onDestroy.mockReset();
_run.mockClear();
_run.mockReset();
});
test('singleControllerNoTransition', () => {
const rootCtl = new MockController('root');
runControllerTree(rootCtl);
expect(_run).toHaveBeenCalledTimes(1);
expect(_run).toHaveBeenCalledWith('root');
});
test('singleControllerThreeTransitions', () => {
const rootCtl = new MockController('root');
_run.mockImplementation(() => {
if (rootCtl.state === 'idle') {
rootCtl.setState('state1');
} else if (rootCtl.state === 'state1') {
rootCtl.setState('state2');
}
});
runControllerTree(rootCtl);
expect(_run).toHaveBeenCalledTimes(3);
expect(_run).toHaveBeenCalledWith('root');
});
test('nestedControllers', () => {
const rootCtl = new MockController('root');
let nextState: MockStates = 'idle';
_run.mockImplementation((type: string) => {
if (type !== 'root') return;
rootCtl.setState(nextState);
if (rootCtl.state === 'idle') return;
if (rootCtl.state === 'state1') {
return [Child('child1', MockController, 'child1')];
}
if (rootCtl.state === 'state2') {
return [
Child('child1', MockController, 'child1'),
Child('child2', MockController, 'child2'),
];
}
if (rootCtl.state === 'state3') {
return [
Child('child1', MockController, 'child1'),
Child('child3', MockController, 'child3'),
];
}
throw new Error('Not reached');
});
runControllerTree(rootCtl);
expect(_run).toHaveBeenCalledWith('root');
expect(_run).toHaveBeenCalledTimes(1);
// Transition the root controller to state1. This will create the first child
// and re-run both (because of the idle -> state1 transition).
_run.mockClear();
_onCreate.mockClear();
nextState = 'state1';
runControllerTree(rootCtl);
expect(_onCreate).toHaveBeenCalledWith('child1');
expect(_onCreate).toHaveBeenCalledTimes(1);
expect(_run).toHaveBeenCalledWith('root');
expect(_run).toHaveBeenCalledWith('child1');
expect(_run).toHaveBeenCalledTimes(4);
// Transition the root controller to state2. This will create the 2nd child
// and run the three of them (root + 2 chilren) two times.
_run.mockClear();
_onCreate.mockClear();
nextState = 'state2';
runControllerTree(rootCtl);
expect(_onCreate).toHaveBeenCalledWith('child2');
expect(_onCreate).toHaveBeenCalledTimes(1);
expect(_run).toHaveBeenCalledWith('root');
expect(_run).toHaveBeenCalledWith('child1');
expect(_run).toHaveBeenCalledWith('child2');
expect(_run).toHaveBeenCalledTimes(6);
// Transition the root controller to state3. This will create the 3rd child
// and remove the 2nd one.
_run.mockClear();
_onCreate.mockClear();
nextState = 'state3';
runControllerTree(rootCtl);
expect(_onCreate).toHaveBeenCalledWith('child3');
expect(_onDestroy).toHaveBeenCalledWith('child2');
expect(_onCreate).toHaveBeenCalledTimes(1);
expect(_run).toHaveBeenCalledWith('root');
expect(_run).toHaveBeenCalledWith('child1');
expect(_run).toHaveBeenCalledWith('child3');
expect(_run).toHaveBeenCalledTimes(6);
// Finally transition back to the idle state. All children should be removed.
_run.mockClear();
_onCreate.mockClear();
_onDestroy.mockClear();
nextState = 'idle';
runControllerTree(rootCtl);
expect(_onDestroy).toHaveBeenCalledWith('child1');
expect(_onDestroy).toHaveBeenCalledWith('child3');
expect(_onCreate).toHaveBeenCalledTimes(0);
expect(_onDestroy).toHaveBeenCalledTimes(2);
expect(_run).toHaveBeenCalledWith('root');
expect(_run).toHaveBeenCalledTimes(2);
});