• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2018 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {
16  Child,
17  Controller,
18} from './controller';
19
20const _onCreate = jest.fn();
21const _onDestroy = jest.fn();
22const _run = jest.fn();
23
24type MockStates = 'idle'|'state1'|'state2'|'state3';
25class MockController extends Controller<MockStates> {
26  constructor(public type: string) {
27    super('idle');
28    _onCreate(this.type);
29  }
30
31  run() {
32    return _run(this.type);
33  }
34
35  onDestroy() {
36    return _onDestroy(this.type);
37  }
38}
39
40function runControllerTree(rootController: MockController): void {
41  for (let runAgain = true, i = 0; runAgain; i++) {
42    if (i >= 100) throw new Error('Controller livelock');
43    runAgain = rootController.invoke();
44  }
45}
46
47beforeEach(() => {
48  _onCreate.mockClear();
49  _onCreate.mockReset();
50  _onDestroy.mockClear();
51  _onDestroy.mockReset();
52  _run.mockClear();
53  _run.mockReset();
54});
55
56test('singleControllerNoTransition', () => {
57  const rootCtl = new MockController('root');
58  runControllerTree(rootCtl);
59  expect(_run).toHaveBeenCalledTimes(1);
60  expect(_run).toHaveBeenCalledWith('root');
61});
62
63test('singleControllerThreeTransitions', () => {
64  const rootCtl = new MockController('root');
65  _run.mockImplementation(() => {
66    if (rootCtl.state === 'idle') {
67      rootCtl.setState('state1');
68    } else if (rootCtl.state === 'state1') {
69      rootCtl.setState('state2');
70    }
71  });
72  runControllerTree(rootCtl);
73  expect(_run).toHaveBeenCalledTimes(3);
74  expect(_run).toHaveBeenCalledWith('root');
75});
76
77test('nestedControllers', () => {
78  const rootCtl = new MockController('root');
79  let nextState: MockStates = 'idle';
80  _run.mockImplementation((type: string) => {
81    if (type !== 'root') return;
82    rootCtl.setState(nextState);
83    if (rootCtl.state === 'idle') return;
84
85    if (rootCtl.state === 'state1') {
86      return [
87        Child('child1', MockController, 'child1'),
88      ];
89    }
90    if (rootCtl.state === 'state2') {
91      return [
92        Child('child1', MockController, 'child1'),
93        Child('child2', MockController, 'child2'),
94      ];
95    }
96    if (rootCtl.state === 'state3') {
97      return [
98        Child('child1', MockController, 'child1'),
99        Child('child3', MockController, 'child3'),
100      ];
101    }
102    throw new Error('Not reached');
103  });
104  runControllerTree(rootCtl);
105  expect(_run).toHaveBeenCalledWith('root');
106  expect(_run).toHaveBeenCalledTimes(1);
107
108  // Transition the root controller to state1. This will create the first child
109  // and re-run both (because of the idle -> state1 transition).
110  _run.mockClear();
111  _onCreate.mockClear();
112  nextState = 'state1';
113  runControllerTree(rootCtl);
114  expect(_onCreate).toHaveBeenCalledWith('child1');
115  expect(_onCreate).toHaveBeenCalledTimes(1);
116  expect(_run).toHaveBeenCalledWith('root');
117  expect(_run).toHaveBeenCalledWith('child1');
118  expect(_run).toHaveBeenCalledTimes(4);
119
120  // Transition the root controller to state2. This will create the 2nd child
121  // and run the three of them (root + 2 chilren) two times.
122  _run.mockClear();
123  _onCreate.mockClear();
124  nextState = 'state2';
125  runControllerTree(rootCtl);
126  expect(_onCreate).toHaveBeenCalledWith('child2');
127  expect(_onCreate).toHaveBeenCalledTimes(1);
128  expect(_run).toHaveBeenCalledWith('root');
129  expect(_run).toHaveBeenCalledWith('child1');
130  expect(_run).toHaveBeenCalledWith('child2');
131  expect(_run).toHaveBeenCalledTimes(6);
132
133  // Transition the root controller to state3. This will create the 3rd child
134  // and remove the 2nd one.
135  _run.mockClear();
136  _onCreate.mockClear();
137  nextState = 'state3';
138  runControllerTree(rootCtl);
139  expect(_onCreate).toHaveBeenCalledWith('child3');
140  expect(_onDestroy).toHaveBeenCalledWith('child2');
141  expect(_onCreate).toHaveBeenCalledTimes(1);
142  expect(_run).toHaveBeenCalledWith('root');
143  expect(_run).toHaveBeenCalledWith('child1');
144  expect(_run).toHaveBeenCalledWith('child3');
145  expect(_run).toHaveBeenCalledTimes(6);
146
147  // Finally transition back to the idle state. All children should be removed.
148  _run.mockClear();
149  _onCreate.mockClear();
150  _onDestroy.mockClear();
151  nextState = 'idle';
152  runControllerTree(rootCtl);
153  expect(_onDestroy).toHaveBeenCalledWith('child1');
154  expect(_onDestroy).toHaveBeenCalledWith('child3');
155  expect(_onCreate).toHaveBeenCalledTimes(0);
156  expect(_onDestroy).toHaveBeenCalledTimes(2);
157  expect(_run).toHaveBeenCalledWith('root');
158  expect(_run).toHaveBeenCalledTimes(2);
159});
160