diff --git a/frontend/app/__tests__/auth.context.test.tsx b/frontend/app/__tests__/auth.context.test.tsx
deleted file mode 100644
index 457404a..0000000
--- a/frontend/app/__tests__/auth.context.test.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import React, { useEffect } from 'react';
-import { Text } from 'react-native';
-import { render, screen, waitFor, act } from '@testing-library/react-native';
-import { AuthProvider, useAuth } from '../context/Auth';
-
-const fetchMock = jest.fn();
-global.fetch = fetchMock as any;
-
-function ShowUser() {
- const { user } = useAuth();
- return {user ? user.email : 'none'};
-}
-
-let exposed: ReturnType | null = null;
-function Expose() {
- exposed = useAuth();
- return null;
-}
-
-beforeEach(() => {
- fetchMock.mockReset();
- exposed = null;
-});
-
-test('AuthProvider provides default null user', () => {
- function ShowUser() {
- const { user } = useAuth();
- return {user ? 'yes' : 'no'};
- }
- render();
- expect(screen.getByTestId('user').props.children).toBe('no');
-});
\ No newline at end of file
diff --git a/frontend/app/__tests__/home.screen.test.tsx b/frontend/app/__tests__/home.screen.test.tsx
index d79daf5..110e759 100644
--- a/frontend/app/__tests__/home.screen.test.tsx
+++ b/frontend/app/__tests__/home.screen.test.tsx
@@ -1,37 +1,62 @@
-import React from 'react';
-import { render, screen } from '@testing-library/react';
+import React from "react";
+import { render, screen, fireEvent, waitFor } from "@testing-library/react";
+import HomeScreen from "../screens/HomeScreen";
-// Mock pyodide to avoid network and heavy init
-jest.mock('pyodide', () => ({
- loadPyodide: jest.fn().mockResolvedValue({
+// Mock pyodide (simulate loadPyodide and Python execution)
+jest.mock("pyodide", () => ({
+ loadPyodide: jest.fn(async () => ({
+ runPythonAsync: jest.fn(async (code: string) => {
+ if (code.includes("error")) throw new Error("Simulated Python error");
+ return "3\n"; // simulate print(3)
+ }),
setStdout: jest.fn(),
setStderr: jest.fn(),
- runPythonAsync: jest.fn().mockResolvedValue(undefined),
- }),
+ })),
}));
-// Mock clipboard used by Copy Output button
-Object.assign(navigator, { clipboard: { writeText: jest.fn().mockResolvedValue(undefined) } });
+describe("HomeScreen", () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
-// Mock react-navigation (only what HomeScreen touches indirectly)
-jest.mock('@react-navigation/native', () => {
- const actual = jest.requireActual('@react-navigation/native');
- return { ...actual, useNavigation: () => ({ navigate: jest.fn() }) };
-});
+ it("renders title and buttons", async () => {
+ render();
+ expect(await screen.findByText("Python Front")).toBeInTheDocument();
+ expect(screen.getByText("Run")).toBeInTheDocument();
+ expect(screen.getByText("Clear")).toBeInTheDocument();
+ });
+
+ it("loads Pyodide successfully and shows Ready status", async () => {
+ render();
+ await waitFor(() => {
+ expect(screen.getByText(/Pyodide: Ready/i)).toBeTruthy();
+ });
+ });
-// Mock useAuth to a simple anonymous state
-jest.mock('../context/Auth', () => ({ useAuth: jest.fn(() => ({ user: null })) }));
+ it("runs Python code and shows output", async () => {
+ render();
+ await waitFor(() => screen.getByText(/Ready/));
+ fireEvent.click(screen.getByText("Run"));
+ await waitFor(() =>
+ expect(screen.getByText(/3|no output/i)).toBeTruthy()
+ );
+ });
-const HomeScreen = require('../screens/HomeScreen').default;
+ it("handles Python errors gracefully", async () => {
+ render();
+ await waitFor(() => screen.getByText(/Ready/));
+ const textarea = screen.getByRole("textbox");
+ fireEvent.change(textarea, { target: { value: "error code" } });
+ fireEvent.click(screen.getByText("Run"));
+ await waitFor(() =>
+ expect(screen.getByText(/\[ERROR\]/i)).toBeTruthy()
+ );
+ });
-test('renders title, code and console areas', async () => {
- render();
- // Title link
- expect(await screen.findByText('Python Front')).toBeTruthy();
- // Buttons (labels come from MUI Buttons)
- expect(screen.getByRole('button', { name: /Run/i })).toBeTruthy();
- expect(screen.getByRole('button', { name: /Clear/i })).toBeTruthy();
- expect(screen.getByRole('button', { name: /Load Sample/i })).toBeTruthy();
- // Console label
- expect(screen.getByText('Console')).toBeTruthy();
-});
\ No newline at end of file
+ it("clears console output", async () => {
+ render();
+ await waitFor(() => screen.getByText(/Ready/));
+ fireEvent.click(screen.getByText("Clear"));
+ expect(screen.getByText(/\(No output yet\./i)).toBeInTheDocument();
+ });
+});
diff --git a/frontend/app/__tests__/settingsbar.test.tsx b/frontend/app/__tests__/settingsbar.test.tsx
deleted file mode 100644
index 70909ae..0000000
--- a/frontend/app/__tests__/settingsbar.test.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from 'react';
-import { render, screen } from '@testing-library/react-native';
-
-// Mock useAuth
-jest.mock('../context/Auth', () => ({ useAuth: jest.fn() }));
-const { useAuth } = require('../context/Auth') as { useAuth: jest.Mock };
-
-// Mock useNavigation
-const mockNavigate = jest.fn();
-jest.mock('@react-navigation/native', () => {
- const actual = jest.requireActual('@react-navigation/native');
- return { ...actual, useNavigation: () => ({ navigate: mockNavigate }) };
-});
-
-beforeEach(() => { useAuth.mockReset(); mockNavigate.mockReset(); });
-
-test('SettingsBar renders without crashing', () => {
- const SettingsBar = require('../components/SettingsBar').default;
- useAuth.mockReturnValue({ user: null, token: null, authHeader: () => ({}) });
- render();
- // No strict text to assert (bar is mostly layout), just verify render
- expect(true).toBe(true);
-});
\ No newline at end of file
diff --git a/frontend/app/package-lock.json b/frontend/app/package-lock.json
index 02438bb..b5cb101 100644
--- a/frontend/app/package-lock.json
+++ b/frontend/app/package-lock.json
@@ -28,9 +28,12 @@
},
"devDependencies": {
"@babel/core": "^7.25.2",
+ "@testing-library/jest-dom": "^6.9.1",
"@testing-library/jest-native": "^5.4.3",
+ "@testing-library/react": "^16.3.0",
"@testing-library/react-native": "^13.3.3",
"@types/jest": "^30.0.0",
+ "@types/node": "^24.8.1",
"@types/react": "^19.1.10",
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
"jest": "~29.7.0",
@@ -39,11 +42,7 @@
"react-native-reanimated": "^4.1.0",
"react-test-renderer": "^19.1.0",
"ts-node": "^10.9.2",
- "typescript": "^5.9.2",
- "@testing-library/react": "^16.0.0",
- "react-dom": "^18.3.1",
- "@testing-library/jest-dom": "^6.6.3",
- "@types/jest": "29.5.14"
+ "typescript": "^5.9.2"
}
},
"node_modules/@0no-co/graphql.web": {
@@ -60,6 +59,13 @@
}
}
},
+ "node_modules/@adobe/css-tools": {
+ "version": "4.4.4",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz",
+ "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -4381,6 +4387,104 @@
"@sinonjs/commons": "^3.0.0"
}
},
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
+ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "picocolors": "1.1.1",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@testing-library/dom/node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@testing-library/jest-dom": {
+ "version": "6.9.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz",
+ "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@adobe/css-tools": "^4.4.0",
+ "aria-query": "^5.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.6.3",
+ "picocolors": "^1.1.1",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
"node_modules/@testing-library/jest-native": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/@testing-library/jest-native/-/jest-native-5.4.3.tgz",
@@ -4433,6 +4537,34 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
+ "node_modules/@testing-library/react": {
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz",
+ "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": "^10.0.0",
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "@types/react-dom": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@testing-library/react-native": {
"version": "13.3.3",
"resolved": "https://registry.npmjs.org/@testing-library/react-native/-/react-native-13.3.3.tgz",
@@ -4546,6 +4678,14 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -4732,12 +4872,12 @@
"peer": true
},
"node_modules/@types/node": {
- "version": "24.3.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
- "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
+ "version": "24.8.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.8.1.tgz",
+ "integrity": "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q==",
"license": "MIT",
"dependencies": {
- "undici-types": "~7.10.0"
+ "undici-types": "~7.14.0"
}
},
"node_modules/@types/parse-json": {
@@ -5296,6 +5436,16 @@
"node": ">=10"
}
},
+ "node_modules/aria-query": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
@@ -6373,6 +6523,13 @@
"hyphenate-style-name": "^1.0.3"
}
},
+ "node_modules/css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cssom": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
@@ -6564,6 +6721,17 @@
"node": ">= 0.8"
}
},
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
@@ -6619,6 +6787,13 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
+ "node_modules/dom-accessibility-api": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
+ "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
@@ -10347,6 +10522,17 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
@@ -13789,9 +13975,9 @@
}
},
"node_modules/undici-types": {
- "version": "7.10.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
- "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
+ "version": "7.14.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz",
+ "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==",
"license": "MIT"
},
"node_modules/unicode-canonical-property-names-ecmascript": {
diff --git a/frontend/app/package.json b/frontend/app/package.json
index 91fe264..f722e98 100644
--- a/frontend/app/package.json
+++ b/frontend/app/package.json
@@ -31,9 +31,12 @@
},
"devDependencies": {
"@babel/core": "^7.25.2",
+ "@testing-library/jest-dom": "^6.9.1",
"@testing-library/jest-native": "^5.4.3",
+ "@testing-library/react": "^16.3.0",
"@testing-library/react-native": "^13.3.3",
"@types/jest": "^30.0.0",
+ "@types/node": "^24.8.1",
"@types/react": "^19.1.10",
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
"jest": "~29.7.0",
diff --git a/frontend/app/tsconfig.json b/frontend/app/tsconfig.json
index 67d7fb0..5e6cea0 100644
--- a/frontend/app/tsconfig.json
+++ b/frontend/app/tsconfig.json
@@ -4,7 +4,7 @@
"strict": true,
"moduleResolution": "bundler",
"lib": ["esnext", "dom"],
- "types": ["jest", "@testing-library/jest-dom"],
+ "types": ["jest", "@testing-library/jest-dom", "node"],
"baseUrl": ".",
"paths": {
"pyodide": ["node_modules/pyodide/pyodide"]