Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@

/* global device, element, by, expect */

describe('Sanity', () => {
beforeEach(async () => {
await device.reloadReactNative();
describe('Button', () => {
beforeAll(async () => {
await element(by.id('explorer_search')).replaceText('<Button>');
await element(by.label('<Button> Simple React Native button component.')).tap();
});

afterEach(async () => {
afterAll(async () => {
//TODO - remove app state persistency, till then, we must go back to main screen,
await element(by.label('Back')).tap();
});
Expand Down
83 changes: 83 additions & 0 deletions RNTester/e2e/__tests__/Switch-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/

/* global device, element, by, expect, waitFor */

const jestExpect = require('expect');

describe.only('Switch', () => {
beforeAll(async () => {
await element(by.id('explorer_search')).replaceText('<Switch>');
await element(by.label('<Switch> Native boolean input')).tap();
});

afterAll(async () => {
await element(by.label('Back')).tap();
});

it('Switch that starts on should switch', async () => {
const testID = 'on-off-initial-off';
const indicatorID = 'on-off-initial-off-indicator';

await expect(element(by.id(testID))).toHaveValue('0');
await expect(element(by.id(indicatorID))).toHaveText('Off');
await element(by.id(testID)).tap();
await expect(element(by.id(testID))).toHaveValue('1');
await expect(element(by.id(indicatorID))).toHaveText('On');
});

it('Switch that starts off should switch', async () => {
const testID = 'on-off-initial-on';
const indicatorID = 'on-off-initial-on-indicator';

await expect(element(by.id(testID))).toHaveValue('1');
await expect(element(by.id(indicatorID))).toHaveText('On');
await element(by.id(testID)).tap();
await expect(element(by.id(testID))).toHaveValue('0');
await expect(element(by.id(indicatorID))).toHaveText('Off');
});

it('disabled switch should not toggle', async () => {
const onTestID = 'disabled-initial-on';
const offTestID = 'disabled-initial-off';
const onIndicatorID = 'disabled-initial-on-indicator';
const offIndicatorID = 'disabled-initial-off-indicator';

await expect(element(by.id(onTestID))).toHaveValue('1');
await expect(element(by.id(onIndicatorID))).toHaveText('On');

try {
Copy link
Member Author

@elicwhite elicwhite Dec 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels gross. When tapping on an element that is disabled, EarlGrey throws the following error:

err:  Cannot perform action due to constraint(s) failure.
    Exception with Action: {
      "Action Name":  "Tap",
      "Element Description":  "<RCTSwitch:0x7f91a1615b60; AX=Y; AX.id='disabled-initial-on'; AX.value='1'; AX.frame={{20.5, 295}, {51, 31}}; AX.activationPoint={46, 310.5}; AX.traits='UIAccessibilityTraitButton'; AX.focused='N'; frame={{0, 0}, {51, 31}}; opaque; alpha=1; disabled>",
      "Failed Constraint(s)":  "enabled",
      "All Constraint(s)":  "(!(isSystemAlertViewShown) && ((respondsToSelector(isAccessibilityElement) && isAccessibilityElement) || kindOfClass('UIView')) && (enabled && !(((kindOfClass('UIView') || respondsToSelector(accessibilityContainer)) && ancestorThatMatches(!(enabled))))) && interactable)",
      "Recovery Suggestion":  "Adjust element properties so that it matches the failed constraint(s)."
    }
    
    
    [
      {
        "Description":  "Cannot perform action due to constraint(s) failure.",
        "Error Domain":  "com.google.earlgrey.ElementInteractionErrorDomain",
        "Error Code":  "1",
        "File Name":  "GREYBaseAction.m",
        "Function Name":  "-[GREYBaseAction satisfiesConstraintsForElement:error:]",
        "Line":  "66"
      }
    ]

any ideas of a cleaner way to handle this case @rotemmiz?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should definitely have better error logs, but what do you mean by cleaner way?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One example of something cleaner would be using jest's expect .toThrow. In this example it would look like this:

await jestExpect(element(by.id(onTestID)).tap()).rejects.toThrow('Cannot perform action due to constraint(s) failure');

However, the error we get back is actually an Error object, with the message field set to an Error, with that's message field set to the long string, making it impossible to assert as if it was a normal error.

await element(by.id(onTestID)).tap();
throw new Error('Does not match');
} catch (err) {
jestExpect(err.message.message).toEqual(
jestExpect.stringContaining(
'Cannot perform action due to constraint(s) failure',
),
);
}
await expect(element(by.id(onTestID))).toHaveValue('1');
await expect(element(by.id(onIndicatorID))).toHaveText('On');

await expect(element(by.id(offTestID))).toHaveValue('0');
await expect(element(by.id(offIndicatorID))).toHaveText('Off');
try {
await element(by.id(offTestID)).tap();
throw new Error('Does not match');
} catch (err) {
jestExpect(err.message.message).toEqual(
jestExpect.stringContaining(
'Cannot perform action due to constraint(s) failure',
),
);
}
await expect(element(by.id(offTestID))).toHaveValue('0');
await expect(element(by.id(offIndicatorID))).toHaveText('Off');
});
});
103 changes: 88 additions & 15 deletions RNTester/js/SwitchExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,37 @@
'use strict';

const React = require('react');
const ReactNative = require('react-native');
const {Switch, Text, View} = ReactNative;
const {Switch, Text, View} = require('react-native');

class BasicSwitchExample extends React.Component<{}, $FlowFixMeState> {
type OnOffIndicatorProps = $ReadOnly<{|on: boolean, testID: string|}>;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking I'll end up pulling these out to use on more tests but I don't want to overabstract until we know they are needed

function OnOffIndicator({on, testID}: OnOffIndicatorProps) {
return <Text testID={testID}>{on ? 'On' : 'Off'}</Text>;
}

type ExampleRowProps = $ReadOnly<{|children: React.Node|}>;
function ExampleRow({children}: ExampleRowProps) {
return (
<View
style={{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

react-native/no-inline-styles: Inline style: { flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 10 }

flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 10,
}}>
{children}
</View>
);
}

type SimpleSwitchExampleState = $ReadOnly<{|
trueSwitchIsOn: boolean,
falseSwitchIsOn: boolean,
|}>;

class BasicSwitchExample extends React.Component<
{||},
SimpleSwitchExampleState,
> {
state = {
trueSwitchIsOn: true,
falseSwitchIsOn: false,
Expand All @@ -23,26 +50,72 @@ class BasicSwitchExample extends React.Component<{}, $FlowFixMeState> {
render() {
return (
<View>
<Switch
onValueChange={value => this.setState({falseSwitchIsOn: value})}
style={{marginBottom: 10}}
value={this.state.falseSwitchIsOn}
/>
<Switch
onValueChange={value => this.setState({trueSwitchIsOn: value})}
value={this.state.trueSwitchIsOn}
/>
<ExampleRow>
<Switch
testID="on-off-initial-off"
onValueChange={value => this.setState({falseSwitchIsOn: value})}
value={this.state.falseSwitchIsOn}
/>
<OnOffIndicator
on={this.state.falseSwitchIsOn}
testID="on-off-initial-off-indicator"
/>
</ExampleRow>
<ExampleRow>
<Switch
testID="on-off-initial-on"
onValueChange={value => this.setState({trueSwitchIsOn: value})}
value={this.state.trueSwitchIsOn}
/>
<OnOffIndicator
on={this.state.trueSwitchIsOn}
testID="on-off-initial-on-indicator"
/>
</ExampleRow>
</View>
);
}
}

class DisabledSwitchExample extends React.Component<{}> {
class DisabledSwitchExample extends React.Component<
{||},
SimpleSwitchExampleState,
> {
state = {
trueSwitchIsOn: true,
falseSwitchIsOn: false,
};

render() {
return (
<View>
<Switch disabled={true} style={{marginBottom: 10}} value={true} />
<Switch disabled={true} value={false} />
<ExampleRow>
<Switch
testID="disabled-initial-off"
disabled={true}
onValueChange={value => this.setState({falseSwitchIsOn: value})}
value={this.state.falseSwitchIsOn}
/>

<OnOffIndicator
on={this.state.falseSwitchIsOn}
testID="disabled-initial-off-indicator"
/>
</ExampleRow>

<ExampleRow>
<Switch
testID="disabled-initial-on"
disabled={true}
onValueChange={value => this.setState({trueSwitchIsOn: value})}
value={this.state.trueSwitchIsOn}
/>

<OnOffIndicator
on={this.state.trueSwitchIsOn}
testID="disabled-initial-on-indicator"
/>
</ExampleRow>
</View>
);
}
Expand Down