• 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 {Child, Controller} from './controller';
16
17const _onCreate = jest.fn();
18const _onDestroy = jest.fn();
19const _run = jest.fn();
20
21type MockStates = 'idle' | 'state1' | 'state2' | 'state3';
22class MockController extends Controller<MockStates> {
23  constructor(public type: string) {
24    super('idle');
25    _onCreate(this.type);
26  }
27
28  run() {
29    return _run(this.type);
30  }
31
32  onDestroy() {
33    return _onDestroy(this.type);
34  }
35}
36
37function runControllerTree(rootController: MockController): void {
38  for (let runAgain = true, i = 0; runAgain; i++) {
39    if (i >= 100) throw new Error('Controller livelock');
40    runAgain = rootController.invoke();
41  }
42}
43
44beforeEach(() => {
45  _onCreate.mockClear();
46  _onCreate.mockReset();
47  _onDestroy.mockClear();
48  _onDestroy.mockReset();
49  _run.mockClear();
50  _run.mockReset();
51});
52
53test('singleControllerNoTransition', () => {
54  const rootCtl = new MockController('root');
55  runControllerTree(rootCtl);
56  expect(_run).toHaveBeenCalledTimes(1);
57  expect(_run).toHaveBeenCalledWith('root');
58});
59
60test('singleControllerThreeTransitions', () => {
61  const rootCtl = new MockController('root');
62  _run.mockImplementation(() => {
63    if (rootCtl.state === 'idle') {
64      rootCtl.setState('state1');
65    } else if (rootCtl.state === 'state1') {
66      rootCtl.setState('state2');
67    }
68  });
69  runControllerTree(rootCtl);
70  expect(_run).toHaveBeenCalledTimes(3);
71  expect(_run).toHaveBeenCalledWith('root');
72});
73
74test('nestedControllers', () => {
75  const rootCtl = new MockController('root');
76  let nextState: MockStates = 'idle';
77  _run.mockImplementation((type: string) => {
78    if (type !== 'root') return;
79    rootCtl.setState(nextState);
80    if (rootCtl.state === 'idle') return;
81
82    if (rootCtl.state === 'state1') {
83      return [Child('child1', MockController, 'child1')];
84    }
85    if (rootCtl.state === 'state2') {
86      return [
87        Child('child1', MockController, 'child1'),
88        Child('child2', MockController, 'child2'),
89      ];
90    }
91    if (rootCtl.state === 'state3') {
92      return [
93        Child('child1', MockController, 'child1'),
94        Child('child3', MockController, 'child3'),
95      ];
96    }
97    throw new Error('Not reached');
98  });
99  runControllerTree(rootCtl);
100  expect(_run).toHaveBeenCalledWith('root');
101  expect(_run).toHaveBeenCalledTimes(1);
102
103  // Transition the root controller to state1. This will create the first child
104  // and re-run both (because of the idle -> state1 transition).
105  _run.mockClear();
106  _onCreate.mockClear();
107  nextState = 'state1';
108  runControllerTree(rootCtl);
109  expect(_onCreate).toHaveBeenCalledWith('child1');
110  expect(_onCreate).toHaveBeenCalledTimes(1);
111  expect(_run).toHaveBeenCalledWith('root');
112  expect(_run).toHaveBeenCalledWith('child1');
113  expect(_run).toHaveBeenCalledTimes(4);
114
115  // Transition the root controller to state2. This will create the 2nd child
116  // and run the three of them (root + 2 chilren) two times.
117  _run.mockClear();
118  _onCreate.mockClear();
119  nextState = 'state2';
120  runControllerTree(rootCtl);
121  expect(_onCreate).toHaveBeenCalledWith('child2');
122  expect(_onCreate).toHaveBeenCalledTimes(1);
123  expect(_run).toHaveBeenCalledWith('root');
124  expect(_run).toHaveBeenCalledWith('child1');
125  expect(_run).toHaveBeenCalledWith('child2');
126  expect(_run).toHaveBeenCalledTimes(6);
127
128  // Transition the root controller to state3. This will create the 3rd child
129  // and remove the 2nd one.
130  _run.mockClear();
131  _onCreate.mockClear();
132  nextState = 'state3';
133  runControllerTree(rootCtl);
134  expect(_onCreate).toHaveBeenCalledWith('child3');
135  expect(_onDestroy).toHaveBeenCalledWith('child2');
136  expect(_onCreate).toHaveBeenCalledTimes(1);
137  expect(_run).toHaveBeenCalledWith('root');
138  expect(_run).toHaveBeenCalledWith('child1');
139  expect(_run).toHaveBeenCalledWith('child3');
140  expect(_run).toHaveBeenCalledTimes(6);
141
142  // Finally transition back to the idle state. All children should be removed.
143  _run.mockClear();
144  _onCreate.mockClear();
145  _onDestroy.mockClear();
146  nextState = 'idle';
147  runControllerTree(rootCtl);
148  expect(_onDestroy).toHaveBeenCalledWith('child1');
149  expect(_onDestroy).toHaveBeenCalledWith('child3');
150  expect(_onCreate).toHaveBeenCalledTimes(0);
151  expect(_onDestroy).toHaveBeenCalledTimes(2);
152  expect(_run).toHaveBeenCalledWith('root');
153  expect(_run).toHaveBeenCalledTimes(2);
154});
155