Add translations and testing framework.
This commit is contained in:
93
src/renderer/src/App.test.tsx
Normal file
93
src/renderer/src/App.test.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { Page } from '@playwright/test';
|
||||
|
||||
// src/renderer/src/App.test.tsx
|
||||
|
||||
// Mock data
|
||||
const mockUser = {
|
||||
uid: 'test123',
|
||||
email: 'test@example.com',
|
||||
displayName: 'Test User',
|
||||
toJSON: () => ({
|
||||
uid: 'test123',
|
||||
email: 'test@example.com',
|
||||
displayName: 'Test User'
|
||||
})
|
||||
};
|
||||
|
||||
test.describe('App Component', () => {
|
||||
let page: Page;
|
||||
|
||||
test.beforeEach(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
|
||||
// Mock Firebase Auth
|
||||
await page.addInitScript(() => {
|
||||
window.mockAuthState = null;
|
||||
|
||||
// Mock the firebase auth module
|
||||
jest.mock('./util/firebase', () => ({
|
||||
auth: {
|
||||
onAuthStateChanged: (callback) => {
|
||||
callback(window.mockAuthState);
|
||||
// Return mock unsubscribe function
|
||||
return () => {};
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Mock electron IPC
|
||||
window.electron = {
|
||||
ipcRenderer: {
|
||||
send: jest.fn()
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('should show SignInForm when user is not authenticated', async () => {
|
||||
await page.evaluate(() => {
|
||||
window.mockAuthState = null;
|
||||
});
|
||||
|
||||
await page.reload();
|
||||
|
||||
// Check if SignInForm is visible
|
||||
const signInForm = await page.locator('form').filter({ hasText: 'Sign In' });
|
||||
await expect(signInForm).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show routes when user is authenticated', async () => {
|
||||
await page.evaluate((user) => {
|
||||
window.mockAuthState = user;
|
||||
}, mockUser);
|
||||
|
||||
await page.reload();
|
||||
|
||||
// Check if AuthHome is visible
|
||||
const authHome = await page.locator('div:text("AuthHome")');
|
||||
await expect(authHome).toBeVisible();
|
||||
|
||||
// Check that electron IPC was called with auth state
|
||||
await expect(page.evaluate(() => {
|
||||
return window.electron.ipcRenderer.send.mock.calls.length > 0;
|
||||
})).resolves.toBe(true);
|
||||
});
|
||||
|
||||
test('should navigate to settings page when authenticated', async () => {
|
||||
await page.evaluate((user) => {
|
||||
window.mockAuthState = user;
|
||||
}, mockUser);
|
||||
|
||||
await page.reload();
|
||||
|
||||
// Navigate to settings
|
||||
await page.click('a[href="/settings"]');
|
||||
|
||||
// Check if Settings page is visible
|
||||
const settingsPage = await page.locator('div:text("Settings")');
|
||||
await expect(settingsPage).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -1,25 +1,49 @@
|
||||
import { Button } from "antd";
|
||||
import log from "electron-log/renderer";
|
||||
import "@ant-design/v5-patch-for-react-19";
|
||||
import { Layout } from "antd";
|
||||
import { User } from "firebase/auth";
|
||||
import { useState } from "react";
|
||||
import { BrowserRouter, Route, Routes } from "react-router";
|
||||
import ipcTypes from "../../util/ipcTypes.json";
|
||||
import NavigationHeader from "./components/NavigationHeader/Navigationheader";
|
||||
import SignInForm from "./components/SignInForm/SignInForm";
|
||||
import Versions from "./components/Versions";
|
||||
import { auth } from "./util/firebase";
|
||||
import {} from "react-error-boundary";
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
import ErrorBoundaryFallback from "./components/ErrorBoundaryFallback/ErrorBoundaryFallback";
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
|
||||
auth.onAuthStateChanged((user) => {
|
||||
setUser(user);
|
||||
//Send back to the main process so that it knows we are authenticated.
|
||||
if (user) {
|
||||
window.electron.ipcRenderer.send(
|
||||
ipcTypes.toMain.authStateChanged,
|
||||
user.toJSON()
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function App(): JSX.Element {
|
||||
const ipcHandle = (): void => window.electron.ipcRenderer.send("ping");
|
||||
const ipcHandleWithType = (): void => {
|
||||
log.error("Test from renderer.");
|
||||
window.electron.ipcRenderer.send(ipcTypes.toMain.test, { test: "test" });
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Button onClick={ipcHandle}>Send IPC</Button>
|
||||
|
||||
<Button onClick={ipcHandleWithType}>Send IPC written by me.</Button>
|
||||
{import.meta.env.VITE_FIREBASE_CONFIG}
|
||||
<Versions></Versions>
|
||||
<SignInForm />
|
||||
</>
|
||||
<BrowserRouter>
|
||||
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
|
||||
<Layout>
|
||||
{!user ? (
|
||||
<SignInForm />
|
||||
) : (
|
||||
<>
|
||||
<NavigationHeader />
|
||||
<Routes>
|
||||
<Route path="/" element={<div>AuthHome</div>} />
|
||||
<Route path="settings" element={<div>Settings</div>} />
|
||||
</Routes>
|
||||
</>
|
||||
)}
|
||||
</Layout>
|
||||
</ErrorBoundary>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Button, Result } from "antd";
|
||||
import { FallbackProps } from "react-error-boundary";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const ErrorBoundaryFallback: React.FC<FallbackProps> = ({
|
||||
error,
|
||||
resetErrorBoundary,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Result
|
||||
status={"500"}
|
||||
title={t("app.errors.errorboundary")}
|
||||
subTitle={error?.message}
|
||||
extra={[<Button onClick={resetErrorBoundary}>Try again</Button>]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorBoundaryFallback;
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Layout, Menu } from "antd";
|
||||
import { MenuItemType } from "antd/es/menu/interface";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { NavLink } from "react-router";
|
||||
|
||||
const NavigationHeader: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const menuItems: MenuItemType[] = [
|
||||
{ label: <NavLink to="/">{t("navigation.home")}</NavLink>, key: "home" },
|
||||
{
|
||||
label: <NavLink to="/settings">{t("navigation.settings")}</NavLink>,
|
||||
key: "settings",
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Layout.Header style={{ display: "flex", alignItems: "center" }}>
|
||||
<div className="demo-logo" />
|
||||
<Menu
|
||||
theme="dark"
|
||||
mode="horizontal"
|
||||
defaultSelectedKeys={["2"]}
|
||||
items={menuItems}
|
||||
style={{ flex: 1, minWidth: 0 }}
|
||||
/>
|
||||
</Layout.Header>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavigationHeader;
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./util/i18n";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
|
||||
18
src/renderer/src/util/i18n.ts
Normal file
18
src/renderer/src/util/i18n.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import enTranslations from "../../../util/translations/en-US/renderer.json";
|
||||
|
||||
const resources = {
|
||||
en: enTranslations,
|
||||
};
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources,
|
||||
debug: import.meta.env.DEV,
|
||||
lng: "en",
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
Reference in New Issue
Block a user