Skip to content

Commit

Permalink
feat(android-setup): Add code to set the app name from the device in …
Browse files Browse the repository at this point in the history
…the setup store (#2911)

* feat(android-setup): Add code to set the app name from the device in the setup store

The app name is required for the start testing screen, and it can't be set until after the permissions have been granted. It also can't be set in the onEnter function for the start testing step because the onEnter method is asynchronous, and the UI will therefore be visible possibly before the app name is set.

* Fix linting errors

* feat(android-setup): Modify configuringPortForwarding state machine step so that the application name is retrieved after port forwarding is successfully set up

Port forwarding, not detect permissions, is when the device is set up and ready to get the app name.

* Fix typo

Co-authored-by: Dave Tryon <45672944+DaveTryon@users.noreply.github.com>

* Update text for linguistic symmetry

Co-authored-by: Dave Tryon <45672944+DaveTryon@users.noreply.github.com>

Co-authored-by: Dave Tryon <45672944+DaveTryon@users.noreply.github.com>
  • Loading branch information
RobGallo and DaveTryon authored Jun 19, 2020
1 parent 486b0dc commit 63f7ee3
Show file tree
Hide file tree
Showing 11 changed files with 123 additions and 14 deletions.
6 changes: 6 additions & 0 deletions src/electron/flux/store/android-setup-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class AndroidSetupStore extends BaseStoreImpl<AndroidSetupStoreData> {
stepTransition: this.stepTransition,
setSelectedDevice: this.setSelectedDevice,
setAvailableDevices: this.setAvailableDevices,
setApplicationName: this.setApplicationName,
});
}

Expand Down Expand Up @@ -67,4 +68,9 @@ export class AndroidSetupStore extends BaseStoreImpl<AndroidSetupStoreData> {
// emitChange will be called from step transition when the step changes
this.state.availableDevices = devices;
};

private setApplicationName = (appName?: string): void => {
// emitChange will be called from step transition when the step changes
this.state.applicationName = appName;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type AndroidSetupStoreCallbacks = {
stepTransition: AndroidSetupStepTransitionCallback;
setSelectedDevice: (device: DeviceInfo) => void;
setAvailableDevices: (devices: DeviceInfo[]) => void;
setApplicationName: (appName?: string) => void;
};

export type AndroidSetupStateMachineFactory = (
Expand Down
2 changes: 2 additions & 0 deletions src/electron/flux/types/android-setup-store-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ export type AndroidSetupStoreData = {
selectedDevice?: DeviceInfo;

availableDevices?: DeviceInfo[];

applicationName?: string;
};
1 change: 1 addition & 0 deletions src/electron/platform/android/setup/android-setup-deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export type AndroidSetupDeps = {
installService: () => Promise<boolean>;
hasExpectedPermissions: () => Promise<boolean>;
setTcpForwarding: () => Promise<boolean>;
getApplicationName: () => Promise<string>;
};
13 changes: 13 additions & 0 deletions src/electron/platform/android/setup/live-android-setup-deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
PackageInfo,
PermissionInfo,
} from 'electron/platform/android/android-service-configurator';
import { DeviceConfigFetcher } from 'electron/platform/android/device-config-fetcher';
import { AndroidSetupDeps } from 'electron/platform/android/setup/android-setup-deps';

export class LiveAndroidSetupDeps implements AndroidSetupDeps {
Expand All @@ -23,6 +24,7 @@ export class LiveAndroidSetupDeps implements AndroidSetupDeps {
private readonly configStore: UserConfigurationStore,
private readonly apkLocator: AndroidServiceApkLocator,
private readonly userConfigMessageCreator: UserConfigMessageCreator,
private readonly fetchDeviceConfig: DeviceConfigFetcher,
private readonly logger: Logger,
) {}

Expand Down Expand Up @@ -101,6 +103,17 @@ export class LiveAndroidSetupDeps implements AndroidSetupDeps {
return false;
};

public getApplicationName = async (): Promise<string> => {
try {
const config = await this.fetchDeviceConfig(62442);
return config.appIdentifier;
} catch (error) {
this.logger.log(error);
}

return '';
};

private async getInstalledVersion(): Promise<string> {
const info: PackageInfo = await this.serviceConfig.getPackageInfo(this.selectedDeviceId);
return info?.versionName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@ import { AndroidSetupStepConfig } from 'electron/platform/android/setup/android-
export const configuringPortForwarding: AndroidSetupStepConfig = deps => ({
actions: {},
onEnter: async () => {
deps.setApplicationName(); // init

const configured = await deps.setTcpForwarding();
deps.stepTransition(
configured
? 'prompt-connected-start-testing'
: 'prompt-configuring-port-forwarding-failed',
);

if (configured === false) {
deps.stepTransition('prompt-configuring-port-forwarding-failed');
return;
}

// device is good to go; so we can get the current app name
const appName = await deps.getApplicationName();
deps.setApplicationName(appName);
deps.stepTransition('prompt-connected-start-testing');
},
});
5 changes: 4 additions & 1 deletion src/electron/views/renderer-initializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,12 @@ getPersistedData(indexedDBInstance, indexedDBDataKeysToFetch).then(
const dispatcher = new DirectActionMessageDispatcher(interpreter);
const userConfigMessageCreator = new UserConfigMessageCreator(dispatcher);

const fetchDeviceConfig = createDeviceConfigFetcher(axios.get);

const apkLocator: AndroidServiceApkLocator = new AndroidServiceApkLocator(
ipcRendererShim.getAppPath,
);

const androidSetupStore = new AndroidSetupStore(
androidSetupActions,
createAndroidSetupStateMachineFactory(
Expand All @@ -205,6 +208,7 @@ getPersistedData(indexedDBInstance, indexedDBDataKeysToFetch).then(
userConfigurationStore,
apkLocator,
userConfigMessageCreator,
fetchDeviceConfig,
logger,
),
),
Expand Down Expand Up @@ -258,7 +262,6 @@ getPersistedData(indexedDBInstance, indexedDBDataKeysToFetch).then(
]);

const fetchScanResults = createScanResultsFetcher(axios.get);
const fetchDeviceConfig = createDeviceConfigFetcher(axios.get);

const featureFlagsController = new FeatureFlagsController(featureFlagStore, interpreter);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,36 @@ describe('AndroidSetupStore', () => {
stateMachineFactoryMock.verifyAll();
});

it('ensure setApplicationName function results in store update', () => {
const appName = 'Star Wars -- Episode Test';

const initialData: AndroidSetupStoreData = { currentStepId: 'detect-adb' };
const expectedData: AndroidSetupStoreData = {
currentStepId: 'detect-adb',
applicationName: appName,
};

let storeCallbacks: AndroidSetupStoreCallbacks;

const stateMachineFactoryMock = Mock.ofInstance(mockableStateMachineFactory);
stateMachineFactoryMock
.setup(m => m(It.isAny()))
.callback(sc => (storeCallbacks = sc))
.verifiable(Times.once());

const store = new AndroidSetupStore(
new AndroidSetupActions(),
stateMachineFactoryMock.object,
);
store.initialize(initialData);

storeCallbacks.setApplicationName(appName);

expect(store.getState()).toEqual(expectedData);

stateMachineFactoryMock.verifyAll();
});

const createAndroidSetupStoreTester = (
actionToInvoke: keyof AndroidSetupActions,
stateMachineFactory: AndroidSetupStateMachineFactory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
PackageInfo,
PermissionInfo,
} from 'electron/platform/android/android-service-configurator';
import { DeviceConfig } from 'electron/platform/android/device-config';
import { DeviceConfigFetcher } from 'electron/platform/android/device-config-fetcher';
import { LiveAndroidSetupDeps } from 'electron/platform/android/setup/live-android-setup-deps';
import { IMock, Mock, MockBehavior, Times } from 'typemoq';

Expand All @@ -27,6 +29,7 @@ describe('LiveAndroidSetupDeps', () => {
let configStoreMock: IMock<UserConfigurationStore>;
let apkLocatorMock: IMock<AndroidServiceApkLocator>;
let configMessageCreatorMock: IMock<UserConfigMessageCreator>;
let fetchConfigMock: IMock<DeviceConfigFetcher>;
let loggerMock: IMock<Logger>;
let testSubject: LiveAndroidSetupDeps;

Expand All @@ -42,12 +45,14 @@ describe('LiveAndroidSetupDeps', () => {
undefined,
MockBehavior.Strict,
);
fetchConfigMock = Mock.ofInstance((port: number) => new Promise<DeviceConfig>(() => null));
loggerMock = Mock.ofType<Logger>();
testSubject = new LiveAndroidSetupDeps(
serviceConfigFactoryMock.object,
configStoreMock.object,
apkLocatorMock.object,
configMessageCreatorMock.object,
fetchConfigMock.object,
loggerMock.object,
);
});
Expand Down Expand Up @@ -418,6 +423,25 @@ describe('LiveAndroidSetupDeps', () => {
verifyAllMocks();
});

it('getApplicationName returns app name when successful', async () => {
const config: DeviceConfig = {
appIdentifier: 'Wonderful App',
} as DeviceConfig;

const p = new Promise<DeviceConfig>(resolve => resolve(config));

fetchConfigMock
.setup(m => m(62442))
.returns(() => p)
.verifiable();

const appName = await testSubject.getApplicationName();

expect(appName).toEqual(config.appIdentifier);

verifyAllMocks();
});

it('setTcpForwarding returns true if no error', async () => {
serviceConfigMock.setup(m => m.setTcpForwarding(undefined)).verifiable(Times.once());
await initializeServiceConfig();
Expand All @@ -428,4 +452,17 @@ describe('LiveAndroidSetupDeps', () => {

verifyAllMocks();
});

it('getApplicationName returns empty string on error', async () => {
fetchConfigMock
.setup(m => m(62442))
.throws(Error('some error'))
.verifiable();

const appName = await testSubject.getApplicationName();

expect(appName).toEqual('');

verifyAllMocks();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,27 @@ describe('Android setup step: configuringPortForwarding', () => {
});

it('onEnter transitions to prompt-connected-start-testing on success', async () => {
const p = new Promise<boolean>(resolve => resolve(true));
const appName = 'my app name';

const tcpForwardingPromise = new Promise<boolean>(resolve => resolve(true));
const appNamePromise = new Promise<string>(resolve => resolve(appName));

const depsMock = Mock.ofType<AndroidSetupStepConfigDeps>(undefined, MockBehavior.Strict);
depsMock
.setup(m => m.setTcpForwarding())
.returns(_ => p)
.returns(_ => tcpForwardingPromise)
.verifiable(Times.once());

depsMock.setup(m => m.stepTransition('prompt-connected-start-testing'));

depsMock
.setup(m => m.stepTransition('prompt-connected-start-testing'))
.setup(m => m.getApplicationName())
.returns(_ => appNamePromise)
.verifiable(Times.once());

depsMock.setup(m => m.setApplicationName(undefined)).verifiable(Times.once());
depsMock.setup(m => m.setApplicationName(appName)).verifiable(Times.once());

const step = configuringPortForwarding(depsMock.object);
await step.onEnter();

Expand All @@ -42,6 +51,8 @@ describe('Android setup step: configuringPortForwarding', () => {
.returns(_ => p)
.verifiable(Times.once());

depsMock.setup(m => m.setApplicationName(undefined)).verifiable(Times.once());

depsMock
.setup(m => m.stepTransition('prompt-configuring-port-forwarding-failed'))
.verifiable(Times.once());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('Android setup step: detectPermissions', () => {
expect(step.onEnter).toBeDefined();
});

it('onEnter transitions to configuring-port-forwarding as expected', async () => {
it('onEnter transitions to configuring-port-forwarding on success', async () => {
const p = new Promise<boolean>(resolve => resolve(true));

const depsMock = Mock.ofType<AndroidSetupStepConfigDeps>(undefined, MockBehavior.Strict);
Expand All @@ -23,17 +23,15 @@ describe('Android setup step: detectPermissions', () => {
.returns(_ => p)
.verifiable(Times.once());

depsMock
.setup(m => m.stepTransition('configuring-port-forwarding'))
.verifiable(Times.once());
depsMock.setup(m => m.stepTransition('configuring-port-forwarding'));

const step = detectPermissions(depsMock.object);
await step.onEnter();

depsMock.verifyAll();
});

it('onEnter transitions to prompt-install-service as expected', async () => {
it('onEnter transitions to prompt-grant-permissions on failure', async () => {
const p = new Promise<boolean>(resolve => resolve(false));

const depsMock = Mock.ofType<AndroidSetupStepConfigDeps>(undefined, MockBehavior.Strict);
Expand Down

0 comments on commit 63f7ee3

Please sign in to comment.