Skip to content
Open
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
2 changes: 1 addition & 1 deletion .detoxrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const basicLaunchArgs = {
disableAnimations: true,
testingEnvironment: true,
};

function createDetoxURLBlacklistRegex(patterns) {
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ jobs:
android-test:
name: "Android Detox (test)"
runs-on: ubuntu-latest
timeout-minutes: 20
timeout-minutes: 30
needs: android-build
strategy:
matrix:
Expand Down
10 changes: 10 additions & 0 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { SCHEME } from "utils/constants";
import SwitchServerScreen from "screens/SwitchServerScreen";
import EditServerScreen from "screens/EditServerScreen";
import { KeyboardProvider } from "react-native-keyboard-controller";
import QRCodeCameraScreen from "screens/QRCodeCameraScreen";

if (!global.btoa) {
global.btoa = encode;
Expand Down Expand Up @@ -68,6 +69,10 @@ function SwitchServerStack() {
name="AddServer"
component={AddServerScreen}
/>
<SwitchServerStackNav.Screen
name="QRCodeCamera"
component={QRCodeCameraScreen}
/>
</SwitchServerStackNav.Navigator>
);
}
Expand Down Expand Up @@ -128,6 +133,11 @@ function AppNavigator() {
component={AddServerScreen}
options={sheetOpts}
/>
<Stack.Screen
name="QRCodeCamera"
component={QRCodeCameraScreen}
options={sheetOpts}
/>
</>
)}
</Stack.Navigator>
Expand Down
6 changes: 6 additions & 0 deletions app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ export default ({ config }: ConfigContext) =>
},
},
],
[
"expo-camera",
{
recordAudioAndroid: false,
},
],
[
"expo-font",
{
Expand Down
18 changes: 16 additions & 2 deletions components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,26 @@ import IconBack from "@material-symbols/svg-400/rounded/arrow_back_ios_new.svg";

export function CloseIcon() {
const theme = useTheme();
return <IconClose width={32} height={32} fill={theme["text-basic-color"]} />;
return (
<IconClose
width={32}
height={32}
fill={theme["text-basic-color"]}
testID="headerCloseIcon"
/>
);
}

export function BackIcon() {
const theme = useTheme();
return <IconBack width={28} height={28} fill={theme["text-basic-color"]} />;
return (
<IconBack
width={28}
height={28}
fill={theme["text-basic-color"]}
testID="headerBackIcon"
/>
);
}

interface HeaderProps {
Expand Down
45 changes: 45 additions & 0 deletions components/ScanQRCodeButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Button } from "@ui-kitten/components";
import { useTranslation } from "react-i18next";
import { useCameraPermissions } from "expo-camera";
import { Linking } from "react-native";
import { useNavigation } from "@react-navigation/native";
import { testingEnvironment } from "helper/launchArguments";

interface ScanQRCodeButtonProps {
shown: "Onboarding" | "Addserverform";
}

export default function ScanQRCodeButton({ shown }: ScanQRCodeButtonProps) {
const { t } = useTranslation();
const navigation = useNavigation();
const [permission, requestPermission] = useCameraPermissions();

if (!permission) {
return null;
}

return (
<Button
style={{ marginVertical: 8 }}
appearance="outline"
status="primary"
testID={`scanQrcodeButton${shown}`}
onPress={async () => {
if (!testingEnvironment() && !permission.granted) {
const result = await requestPermission();

if (!result.granted) {
if (!result.canAskAgain) {
await Linking.openSettings();
}
return;
}
}

navigation.navigate("QRCodeCamera");
}}
>
{t("servers.manually.qrcode.scan")}
</Button>
);
}
3 changes: 3 additions & 0 deletions components/ServerForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import LoadingIndicator from "./animations/LoadingIndicator";
import { useTranslation } from "react-i18next";
import { BasicAuth, Server } from "types";
import { useAppContext } from "./AppContext";
import ScanQRCodeButton from "./ScanQRCodeButton";

interface ServerFormProps {
server: Server | undefined;
Expand Down Expand Up @@ -182,6 +183,8 @@ export default function ServerForm({
</>
)}

{mode === "create" && <ScanQRCodeButton shown="Addserverform" />}

<Button
style={{ marginTop: 16, marginBottom: 16 }}
appearance="filled"
Expand Down
4 changes: 2 additions & 2 deletions components/animations/ActivityIndicator.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ActivityIndicatorProps, View } from "react-native";
import { ActivityIndicator as RNActivityIndicator } from "react-native";
import { disableAnimations } from "helper/launchArguments";
import { testingEnvironment } from "helper/launchArguments";

export default function ActivityIndicator(props?: ActivityIndicatorProps) {
return (
<View style={{ justifyContent: "center", alignItems: "center" }}>
<RNActivityIndicator {...props} animating={!disableAnimations()} />
<RNActivityIndicator {...props} animating={!testingEnvironment()} />
</View>
);
}
4 changes: 2 additions & 2 deletions components/animations/Spinner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import {
SpinnerProps,
Spinner as UIKittenSpinner,
} from "@ui-kitten/components";
import { disableAnimations } from "helper/launchArguments";
import { testingEnvironment } from "helper/launchArguments";

export default function Spinner(props: SpinnerProps) {
return <UIKittenSpinner {...props} animating={!disableAnimations()} />;
return <UIKittenSpinner {...props} animating={!testingEnvironment()} />;
}
7 changes: 6 additions & 1 deletion e2e/changeServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ describe("Change Server", () => {
await element(by.id("addServerIcon")).tap();
await element(by.id("@serverFormTitle/input")).typeText("Demo");
await element(by.id("@serverFormUrl/input")).typeText("demo.evcc.io");
await element(by.id("serverFormAuth")).swipe("up");
await element(by.id("serverFormCheckAndSave")).tap();

await tapAfterWaitFor(element(by.id("server1")));
Expand All @@ -76,6 +77,7 @@ describe("Change Server", () => {
await element(by.id("addServerIcon")).tap();
await element(by.id("@serverFormTitle/input")).typeText("Demo");
await element(by.id("@serverFormUrl/input")).typeText("demo.evcc.io");
await element(by.id("serverFormAuth")).swipe("up");
await element(by.id("serverFormCheckAndSave")).tap();

// remove active server (server0 = local); first remaining (demo) is auto-activated
Expand All @@ -100,6 +102,7 @@ describe("Change Server", () => {

await element(by.id("@serverFormTitle/input")).typeText("Demo");
await element(by.id("@serverFormUrl/input")).typeText("demo.evcc.io");
await element(by.id("serverFormAuth")).swipe("up");
await element(by.id("serverFormCheckAndSave")).tap();

await tapAfterWaitFor(element(by.id("server1")));
Expand All @@ -122,6 +125,8 @@ describe("Change Server", () => {
await element(by.id("serverFormCheckAndSave")).tap();

// verify the 3rd server was added; switching is covered in "two servers: add and switch"
await waitFor(element(by.id("server2"))).toExist().withTimeout(20000);
await waitFor(element(by.id("server2")))
.toExist()
.withTimeout(20000);
});
});
2 changes: 1 addition & 1 deletion e2e/manualEntry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe("Manual entry", () => {
await expect(element(by.id("@serverFormAuthUser/input"))).not.toExist();
await expect(element(by.id("@serverFormAuthPassword/input"))).not.toExist();

await element(by.id("serverFormAuth")).swipe("up");
await element(by.id("serverFormCheckAndSave")).tap();

await waitForWebview();
Expand All @@ -40,5 +41,4 @@ describe("Manual entry", () => {

await waitForWebview();
});

});
100 changes: 100 additions & 0 deletions e2e/qrcodeCamera.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import "detox";
import { byWebDataTestId, waitForWebview } from "./helper";
import { expect } from "detox";

async function expectServerForm() {
await expect(element(by.id("@serverFormTitle/input"))).toHaveText(
"Local Auth",
);
await expect(element(by.id("@serverFormUrl/input"))).toHaveText(
"http://localhost:7080",
);
await expect(element(by.id("@serverFormAuthUser/input"))).toHaveText("admin");
await expect(element(by.id("@serverFormAuthPassword/input"))).toHaveText(
"secret",
);
}

describe("QRCode", () => {
it("onboarding: open and close", async () => {
await device.launchApp({ resetAppState: true });

await element(by.id("manualEntry")).tap();
await element(by.id("scanQrcodeButtonAddserverform")).tap();
await element(by.id("headerCloseIcon")).tap();
await element(by.id("headerCloseIcon")).tap();
await expect(element(by.id("serverScreenTitle"))).toExist();
});

it("onboarding: add server by qrcode button", async () => {
await device.launchApp({ resetAppState: true });

await element(by.id("manualEntry")).tap();
await element(by.id("scanQrcodeButtonAddserverform")).tap();
await element(by.id("testQrCodeDetected")).tap();

await expectServerForm();

await element(by.id("serverFormCheckAndSave")).tap();
await waitForWebview();
});

it("onboarding: add server by serverform button", async () => {
await device.launchApp({ resetAppState: true });

await element(by.id("scanQrcodeButtonOnboarding")).tap();
await element(by.id("testQrCodeDetected")).tap();

await expectServerForm();

await element(by.id("serverFormCheckAndSave")).tap();
await waitForWebview();
});

it("switchserver: open and close", async () => {
await device.launchApp({
url: "evcc://server?url=localhost:7070&title=siteTitle",
resetAppState: true,
});

await element(by.id("serverFormCheckAndSave")).tap();
await waitForWebview();

await byWebDataTestId("tab-more").tap();
await byWebDataTestId("tab-more-app").tap();

await element(by.id("addServerIcon")).tap();
await element(by.id("scanQrcodeButtonAddserverform")).tap();
await element(by.id("headerCloseIcon")).tap();
await element(by.id("headerBackIcon")).tap();

await waitFor(element(by.id("server0")))
.toExist()
.withTimeout(20000);
await expect(element(by.id("server1"))).not.toExist();
});

it("switchserver: add server by serverform button", async () => {
await device.launchApp({
url: "evcc://server?url=localhost:7070&title=siteTitle",
resetAppState: true,
});

await element(by.id("serverFormCheckAndSave")).tap();
await waitForWebview();

await byWebDataTestId("tab-more").tap();
await byWebDataTestId("tab-more-app").tap();

await element(by.id("addServerIcon")).tap();
await element(by.id("scanQrcodeButtonAddserverform")).tap();
await element(by.id("testQrCodeDetected")).tap();

await expectServerForm();

await element(by.id("serverFormCheckAndSave")).tap();
await waitFor(element(by.id("server1")))
.toExist()
.withTimeout(20000);
});
});
2 changes: 1 addition & 1 deletion e2e/setup-jest.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
jest.retryTimes(2);
jest.retryTimes(1);
6 changes: 3 additions & 3 deletions helper/launchArguments.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { LaunchArguments } from "react-native-launch-arguments";

interface LaunchArgs {
disableAnimations?: boolean;
testingEnvironment?: boolean;
testLegacyServerConfig?: boolean;
}

export function disableAnimations(): boolean {
return !!LaunchArguments.value<LaunchArgs>().disableAnimations;
export function testingEnvironment(): boolean {
return !!LaunchArguments.value<LaunchArgs>().testingEnvironment;
}

export function testLegacyServerConfig(): boolean {
Expand Down
1 change: 0 additions & 1 deletion i18n/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"password": "Heslo",
"qrcode": {
"invalid": "Neplatný EVCC QR kod.",
"permissionDenied": "Povolte přístup ke kameře v nastavení, abyste mohli naskenovat QR kod.",
"recognized": "QR kod detekován!",
"scan": "Naskenujte QR kod",
"scanning": "analyzování..."
Expand Down
1 change: 0 additions & 1 deletion i18n/da.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"password": "Kodeord",
"qrcode": {
"invalid": "Det er ikke en gyldig evcc QR kode.",
"permissionDenied": "Aktivér kameraadgang for at scanne en QR-kode.",
"recognized": "QR code fundet!",
"scan": "Scan QR koden",
"scanning": "Scanner..."
Expand Down
6 changes: 6 additions & 0 deletions i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
"serverNotAvailable": "Server nicht erreichbar. Bitte prüfe die Adresse.",
"specify": "Adresse manuell eingeben",
"user": "Benutzer",
"qrcode": {
"scan": "QR-Code scannen",
"recognized": "QR-Code erkannt!",
"invalid": "Kein gültiger evcc QR-Code.",
"scanning": "erkennen..."
},
"title": "Titel",
"serverExistsAlready": "Dieser Server existiert bereits."
},
Expand Down
6 changes: 6 additions & 0 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
"serverNotAvailable": "Server not available. Please check the address.",
"specify": "Enter address manually",
"user": "User",
"qrcode": {
"scan": "Scan QR code",
"recognized": "QR code detected!",
"invalid": "Not a valid evcc QR code.",
"scanning": "detecting..."
},
"title": "Title",
"serverExistsAlready": "This server already exists."
},
Expand Down
1 change: 0 additions & 1 deletion i18n/et.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"password": "Salasõna",
"qrcode": {
"invalid": "Pole korrektne evcc QR-kood.",
"permissionDenied": "QR-koodi skaneerimiseks luba seadistustest kaamera kasutamine.",
"recognized": "QR-kood on tuvastatud!",
"scan": "Skaneeri QR-koodi",
"scanning": "tuvastan..."
Expand Down
Loading
Loading