// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {assert} from '//resources/js/assert.js';
import {PromiseResolver} from '//resources/js/promise_resolver.js';
/**
* A base class for all test browser proxies to inherit from. Provides helper
* methods for allowing tests to track when a method was called.
* TestBrowserProxy should only be used for BrowserProxy objects. Subclasses of
* TestBrowserProxy should always implement an interface corresponding to the
* real BrowserProxy they are replacing, i.e.:
*
* class MyTestBrowserProxy extends TestBrowserProxy implements MyBrowserProxy
*
* Subclasses are responsible for calling |methodCalled|, when a method is
* called, which will trigger callers of |whenCalled| to get notified.
* For example:
* --------------------------------------------------------------------------
* class MyTestBrowserProxy extends TestBrowserProxy {
* constructor() {
* super(['myMethod']);
* }
*
* myMethod(someId) {
* this.methodCalled('myMethod', someId);
* }
* };
*
* // Test code sample
*
* var testBrowserProxy = new MyTestBrowserProxy();
* // ...Replacing real proxy with test proxy....
* simulateClickFooButton();
* testBrowserProxy.whenCalled('fooMethod').then(function(id) {
* assertEquals(EXPECTED_ID, id);
* });
* --------------------------------------------------------------------------
*/
interface MethodData {
resolver: PromiseResolver<any>;
args: any[];
}
export class TestBrowserProxy {
private resolverMap_: Map<string, MethodData>;
/**
* @param methodNames Names of all methods whose calls need to be tracked.
*/
constructor(methodNames: string[] = []) {
this.resolverMap_ = new Map();
methodNames.forEach(methodName => {
this.createMethodData_(methodName);
});
}
/**
* Called by subclasses when a tracked method is called from the code that
* is being tested.
* @param args Arguments to be forwarded to the testing code, useful for
* checking whether the proxy method was called with the expected
* arguments.
*/
methodCalled(methodName: string, ...args: any[]): void {
const methodData = this.resolverMap_.get(methodName);
assert(methodData);
const storedArgs = args.length === 1 ? args[0] : args;
methodData.args.push(storedArgs);
this.resolverMap_.set(methodName, methodData);
methodData.resolver.resolve(storedArgs);
}
/**
* @return A promise that is resolved when the given method is called.
*/
whenCalled(methodName: string): Promise<any> {
return this.getMethodData_(methodName).resolver.promise;
}
/**
* Resets the PromiseResolver associated with the given method.
*/
resetResolver(methodName: string) {
this.getMethodData_(methodName);
this.createMethodData_(methodName);
}
/**
* Resets all PromiseResolvers.
*/
reset() {
this.resolverMap_.forEach((_value, methodName) => {
this.createMethodData_(methodName);
});
}
/**
* Get number of times method is called.
*/
getCallCount(methodName: string): number {
return this.getMethodData_(methodName).args.length;
}
/**
* Returns the arguments of calls made to |method|.
*/
getArgs(methodName: string): any[] {
return this.getMethodData_(methodName).args;
}
/**
* Try to give programmers help with mistyped methodNames.
*/
private getMethodData_(methodName: string): MethodData {
// Tip: check that the |methodName| is being passed to |this.constructor|.
const methodData = this.resolverMap_.get(methodName);
assert(methodData, `Method '${methodName}' not found in TestBrowserProxy.`);
return methodData;
}
/**
* Creates a new |MethodData| for |methodName|.
*/
private createMethodData_(methodName: string) {
this.resolverMap_.set(
methodName, {resolver: new PromiseResolver(), args: []});
}
}