From 24157b039ac3d62345c3c81e7e1e2744636012bf Mon Sep 17 00:00:00 2001 From: europanite Date: Sun, 19 Oct 2025 20:30:29 +0900 Subject: [PATCH 01/45] fix/readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7f06202..40e8434 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # [Python Front](https://github.com/europanite/python_front "Python Front") +A browser based Python playground. + !["web_ui"](./assets/images/web_ui.png) ## Demo - [Python Front](https://europanite.github.io/python_front/)) - -A browser based Python playground. + [Python Front](https://europanite.github.io/python_front/) --- From 2b63566b96592b4491059784fa5c8e4f4ea2e671 Mon Sep 17 00:00:00 2001 From: europanite Date: Sun, 19 Oct 2025 20:37:24 +0900 Subject: [PATCH 02/45] fix/readme --- assets/images/web_ui.png | Bin 51732 -> 41961 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/images/web_ui.png b/assets/images/web_ui.png index 3d254e44dc9b32aac8a853b4f4fdf56cdc6cd8e9..b4a785ea159db3c54706dfc1053846cce6075b8c 100644 GIT binary patch literal 41961 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 literal 51732 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 z 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 DFO^%M From 68435787f99b216c971774a775b65a2595f4ffec Mon Sep 17 00:00:00 2001 From: europanite Date: Mon, 20 Oct 2025 01:26:35 +0900 Subject: [PATCH 03/45] fix/tests --- frontend/app/__tests__/auth.context.test.tsx | 32 -------- frontend/app/__tests__/home.screen.test.tsx | 81 +++++++++++++------- frontend/app/__tests__/settingsbar.test.tsx | 23 ------ 3 files changed, 53 insertions(+), 83 deletions(-) delete mode 100644 frontend/app/__tests__/auth.context.test.tsx delete mode 100644 frontend/app/__tests__/settingsbar.test.tsx 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 From 242dbec60713a178fefff89dbe9db48429c53356 Mon Sep 17 00:00:00 2001 From: europanite Date: Mon, 20 Oct 2025 01:35:34 +0900 Subject: [PATCH 04/45] fix/tests --- frontend/app/package-lock.json | 58 +++++++++++++++++++++++++++++++--- frontend/app/package.json | 1 + 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/frontend/app/package-lock.json b/frontend/app/package-lock.json index 02438bb..ab96247 100644 --- a/frontend/app/package-lock.json +++ b/frontend/app/package-lock.json @@ -28,6 +28,7 @@ }, "devDependencies": { "@babel/core": "^7.25.2", + "@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-native": "^5.4.3", "@testing-library/react-native": "^13.3.3", "@types/jest": "^30.0.0", @@ -39,11 +40,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 +57,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 +4385,26 @@ "@sinonjs/commons": "^3.0.0" } }, + "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", @@ -5296,6 +5320,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 +6407,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", @@ -6619,6 +6660,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", diff --git a/frontend/app/package.json b/frontend/app/package.json index 91fe264..4ef8d9f 100644 --- a/frontend/app/package.json +++ b/frontend/app/package.json @@ -31,6 +31,7 @@ }, "devDependencies": { "@babel/core": "^7.25.2", + "@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-native": "^5.4.3", "@testing-library/react-native": "^13.3.3", "@types/jest": "^30.0.0", From f19c0e1effb5768e51894390db728b5b3f4e9050 Mon Sep 17 00:00:00 2001 From: europanite Date: Mon, 20 Oct 2025 01:41:32 +0900 Subject: [PATCH 05/45] fix/tests --- frontend/app/package-lock.json | 152 +++++++++++++++++++++++++++++++-- frontend/app/package.json | 2 + frontend/app/tsconfig.json | 2 +- 3 files changed, 148 insertions(+), 8 deletions(-) diff --git a/frontend/app/package-lock.json b/frontend/app/package-lock.json index ab96247..b5cb101 100644 --- a/frontend/app/package-lock.json +++ b/frontend/app/package-lock.json @@ -30,8 +30,10 @@ "@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", @@ -4385,6 +4387,84 @@ "@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", @@ -4457,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", @@ -4570,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", @@ -4756,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": { @@ -6605,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", @@ -10395,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", @@ -13837,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 4ef8d9f..f722e98 100644 --- a/frontend/app/package.json +++ b/frontend/app/package.json @@ -33,8 +33,10 @@ "@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"] From 1d889aca81fe71f1e475face704d2a15d227dca9 Mon Sep 17 00:00:00 2001 From: europanite Date: Mon, 20 Oct 2025 08:10:43 +0900 Subject: [PATCH 06/45] fix/tests --- .github/workflows/{frontend.yml => ci.yml} | 20 ++------------------ .github/workflows/deploy-pages.yml | 2 +- .github/workflows/docker.yml | 2 +- 3 files changed, 4 insertions(+), 20 deletions(-) rename .github/workflows/{frontend.yml => ci.yml} (96%) diff --git a/.github/workflows/frontend.yml b/.github/workflows/ci.yml similarity index 96% rename from .github/workflows/frontend.yml rename to .github/workflows/ci.yml index 041a2c0..b901eba 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,12 @@ -name: Frontend CI - +name: CI on: push: pull_request: - concurrency: group: frontend-ci-${{ github.ref }} cancel-in-progress: true - permissions: contents: read - jobs: test: name: Jest on Node ${{ matrix.node }} @@ -18,40 +14,33 @@ jobs: strategy: fail-fast: false matrix: - node: [18, 20] # LTS + current used in repo - + node: [18, 20, 22] env: CI: "1" EXPO_TUNNEL: "false" EXPO_DEVTOOLS_LISTEN_ADDRESS: 0.0.0.0 EXPO_PUBLIC_API_BASE: http://localhost:8000 - steps: - name: Checkout uses: actions/checkout@v4 - - name: Use Node ${{ matrix.node }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} cache: npm cache-dependency-path: frontend/app/package-lock.json - - name: Install deps working-directory: frontend/app run: npm ci - - name: Type check (skip if no TS) if: hashFiles('frontend/app/tsconfig.json') != '' working-directory: frontend/app run: | npx --yes typescript@latest -v >/dev/null 2>&1 || true npx tsc --noEmit || (echo "::warning::Type check failed"; exit 1) - - name: Run Jest with coverage (CI mode) working-directory: frontend/app run: npm test -- --ci --runInBand --coverage --verbose - - name: Upload coverage if: always() uses: actions/upload-artifact@v4 @@ -59,7 +48,6 @@ jobs: name: coverage-node${{ matrix.node }} path: frontend/app/coverage if-no-files-found: warn - web-export-smoke: name: Expo web export (smoke) needs: test @@ -69,22 +57,18 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Use Node 20 uses: actions/setup-node@v4 with: node-version: 20 cache: npm cache-dependency-path: frontend/app/package-lock.json - - name: Install deps working-directory: frontend/app run: npm ci - - name: Build (expo export -p web) working-directory: frontend/app run: npx expo export -p web - - name: Upload dist (preview artifact) if: always() uses: actions/upload-artifact@v4 diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 98be759..0ccb427 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -1,4 +1,4 @@ -name: Deploy Expo Web to GitHub Pages +name: GitHub Pages on: push: branches: [ "main" ] diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1c199a3..75ef9b8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,4 +1,4 @@ -name: Frontend Tests via Docker +name: docker on: workflow_dispatch: pull_request: From d95fced80be9bd1e285a3353bb3bf2746cee4b16 Mon Sep 17 00:00:00 2001 From: europanite Date: Mon, 20 Oct 2025 08:12:43 +0900 Subject: [PATCH 07/45] fix/tests --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 40e8434..8ab78c6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # [Python Front](https://github.com/europanite/python_front "Python Front") +[![CI](https://github.com/europanite/python_front/actions/workflows/ci.yml/badge.svg)](https://github.com/europanite/python_front/actions/workflows/ci.yml) +[![Deploy Expo Web to GitHub Pages](https://github.com/europanite/python_front/actions/workflows/deploy-pages.yml/badge.svg)](https://github.com/europanite/python_front/actions/workflows/deploy-pages.yml) + A browser based Python playground. !["web_ui"](./assets/images/web_ui.png) From c560771b19dfeb43afc2af2109667405f006e986 Mon Sep 17 00:00:00 2001 From: europanite Date: Mon, 20 Oct 2025 08:13:19 +0900 Subject: [PATCH 08/45] fix/tests --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8ab78c6..0796ca0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # [Python Front](https://github.com/europanite/python_front "Python Front") [![CI](https://github.com/europanite/python_front/actions/workflows/ci.yml/badge.svg)](https://github.com/europanite/python_front/actions/workflows/ci.yml) +[![Frontend Tests via Docker](https://github.com/europanite/python_front/actions/workflows/docker.yml/badge.svg)](https://github.com/europanite/python_front/actions/workflows/docker.yml) [![Deploy Expo Web to GitHub Pages](https://github.com/europanite/python_front/actions/workflows/deploy-pages.yml/badge.svg)](https://github.com/europanite/python_front/actions/workflows/deploy-pages.yml) A browser based Python playground. From 58ba290bdaef239e89df8661dd139c5d665d950e Mon Sep 17 00:00:00 2001 From: europanite Date: Wed, 22 Oct 2025 11:12:05 +0900 Subject: [PATCH 09/45] renamed the repository --- .gitignore | 2 +- README.md | 10 +- SECURITY.md | 4 +- _config.yml | 4 +- frontend/app/app.json | 2 +- frontend/app/public/404.html | 4 +- frontend/app/screens/HomeScreen.tsx | 2 +- python_front_20251020_012446.md | 2214 +++++++++++++++++++++++++++ python_front_20251020_012533.md | 2068 +++++++++++++++++++++++++ python_front_20251020_013059.md | 2148 ++++++++++++++++++++++++++ python_front_20251020_013705.md | 2200 ++++++++++++++++++++++++++ 11 files changed, 8644 insertions(+), 14 deletions(-) create mode 100644 python_front_20251020_012446.md create mode 100644 python_front_20251020_012533.md create mode 100644 python_front_20251020_013059.md create mode 100644 python_front_20251020_013705.md diff --git a/.gitignore b/.gitignore index 1ab30b6..799f6d9 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,4 @@ frontend/app/coverage/ frontend/app/.jest-cache/ make_md.py -python_front_* \ No newline at end of file +browser_based_python_* \ No newline at end of file diff --git a/README.md b/README.md index 0796ca0..3a895b0 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ -# [Python Front](https://github.com/europanite/python_front "Python Front") +# [Browser Based Python](https://github.com/europanite/browser_based_python "Browser Based Python") -[![CI](https://github.com/europanite/python_front/actions/workflows/ci.yml/badge.svg)](https://github.com/europanite/python_front/actions/workflows/ci.yml) -[![Frontend Tests via Docker](https://github.com/europanite/python_front/actions/workflows/docker.yml/badge.svg)](https://github.com/europanite/python_front/actions/workflows/docker.yml) -[![Deploy Expo Web to GitHub Pages](https://github.com/europanite/python_front/actions/workflows/deploy-pages.yml/badge.svg)](https://github.com/europanite/python_front/actions/workflows/deploy-pages.yml) +[![CI](https://github.com/europanite/browser_based_python/actions/workflows/ci.yml/badge.svg)](https://github.com/europanite/browser_based_python/actions/workflows/ci.yml) +[![Frontend Tests via Docker](https://github.com/europanite/browser_based_python/actions/workflows/docker.yml/badge.svg)](https://github.com/europanite/browser_based_python/actions/workflows/docker.yml) +[![Deploy Expo Web to GitHub Pages](https://github.com/europanite/browser_based_python/actions/workflows/deploy-pages.yml/badge.svg)](https://github.com/europanite/browser_based_python/actions/workflows/deploy-pages.yml) A browser based Python playground. !["web_ui"](./assets/images/web_ui.png) ## Demo - [Python Front](https://europanite.github.io/python_front/) + [Python Front](https://europanite.github.io/browser_based_python/) --- diff --git a/SECURITY.md b/SECURITY.md index 66e024c..eec3376 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,7 @@ ## Supported Versions -The following table shows which versions of `python_front` are currently being supported with security updates. +The following table shows which versions of `browser_based_python` are currently being supported with security updates. | Version | Supported | |---------|--------------------| @@ -35,4 +35,4 @@ If you discover a security vulnerability within this project, please help us kee ## Acknowledgements -We deeply appreciate the efforts of security researchers and contributors who help us improve the security of `python_front`. +We deeply appreciate the efforts of security researchers and contributors who help us improve the security of `browser_based_python`. diff --git a/_config.yml b/_config.yml index c352d93..ae96746 100644 --- a/_config.yml +++ b/_config.yml @@ -1,6 +1,6 @@ -title: "python_front" +title: "browser_based_python" description: "A playground for Python" -baseurl: "/python_front" +baseurl: "/browser_based_python" url: "https://europanite.github.io" theme: minima markdown: kramdown diff --git a/frontend/app/app.json b/frontend/app/app.json index 56a2820..8a37079 100644 --- a/frontend/app/app.json +++ b/frontend/app/app.json @@ -23,7 +23,7 @@ "edgeToEdgeEnabled": true }, "experiments": { - "baseUrl": "/python_front" + "baseUrl": "/browser_based_python" }, "web": { "favicon": "./assets/favicon.png", diff --git a/frontend/app/public/404.html b/frontend/app/public/404.html index 9909e84..1d74d9c 100644 --- a/frontend/app/public/404.html +++ b/frontend/app/public/404.html @@ -2,10 +2,10 @@ Redirecting... diff --git a/frontend/app/screens/HomeScreen.tsx b/frontend/app/screens/HomeScreen.tsx index e32f54f..91d02c3 100644 --- a/frontend/app/screens/HomeScreen.tsx +++ b/frontend/app/screens/HomeScreen.tsx @@ -206,7 +206,7 @@ export default function HomeScreen() {

+ +# Project Export: python_front + +## Overview + +- Root: `/home/skinner/python_front` +- Files: **40** +- Total size: **47018 bytes** +- Total LOC: 1433 | SLOC: 1217 | TODOs: 0 + +### Language mix +- tsx: 8 +- plain: 6 +- markdown: 6 +- yaml: 6 +- javascript: 6 +- typescript: 3 +- json: 2 +- html: 2 +- dockerfile: 1 + +### Top 12 largest files (bytes) +- `LICENSE` — 11340 bytes +- `frontend/app/screens/HomeScreen.tsx` — 8293 bytes +- `CODE_OF_CONDUCT.md` — 4085 bytes +- `.github/workflows/frontend.yml` — 2348 bytes +- `frontend/app/components/SettingsBar.tsx` — 1656 bytes +- `frontend/app/__tests__/home.screen.test.tsx` — 1421 bytes +- `.github/workflows/deploy-pages.yml` — 1203 bytes +- `SECURITY.md` — 1161 bytes +- `frontend/app/jest.config.ts` — 1011 bytes +- `frontend/app/context/Auth.tsx` — 1001 bytes +- `frontend/app/App.tsx` — 991 bytes +- `frontend/app/__tests__/settingsbar.test.tsx` — 892 bytes + +### Top 12 longest files (LOC) +- `frontend/app/screens/HomeScreen.tsx` — 271 LOC +- `LICENSE` — 201 LOC +- `.github/workflows/frontend.yml` — 94 LOC +- `CODE_OF_CONDUCT.md` — 68 LOC +- `.gitignore` — 58 LOC +- `frontend/app/components/SettingsBar.tsx` — 51 LOC +- `.github/workflows/deploy-pages.yml` — 47 LOC +- `SECURITY.md` — 38 LOC +- `frontend/app/.gitignore` — 37 LOC +- `frontend/app/__tests__/home.screen.test.tsx` — 37 LOC +- `frontend/app/context/Auth.tsx` — 35 LOC +- `.dockerignore` — 34 LOC + +### Project tree (included subset) +``` +python_front/ +├── .github/ +│ ├── ISSUE_TEMPLATE/ +│ │ └── bug_report.md +│ ├── workflows/ +│ │ ├── deploy-pages.yml +│ │ ├── docker.yml +│ │ └── frontend.yml +│ └── pull_request_template.md +├── frontend/ +│ ├── app/ +│ │ ├── __mocks__/ +│ │ │ ├── expoConstantsMock.js +│ │ │ ├── expoMock.js +│ │ │ └── expoRouterMock.js +│ │ ├── __tests__/ +│ │ │ ├── auth.context.test.tsx +│ │ │ ├── home.screen.test.tsx +│ │ │ ├── settingsbar.test.tsx +│ │ │ └── smoke.test.tsx +│ │ ├── components/ +│ │ │ └── SettingsBar.tsx +│ │ ├── context/ +│ │ │ └── Auth.tsx +│ │ ├── public/ +│ │ │ ├── 404.html +│ │ │ └── google095bf08db4fb15d0.html +│ │ ├── screens/ +│ │ │ └── HomeScreen.tsx +│ │ ├── .gitignore +│ │ ├── app.json +│ │ ├── App.tsx +│ │ ├── babel.config.js +│ │ ├── index.ts +│ │ ├── jest.config.ts +│ │ ├── jest.setup.ts +│ │ ├── jest.setupFiles.js +│ │ ├── metro.config.js +│ │ └── tsconfig.json +│ ├── Dockerfile +│ └── Dockerfile.test +├── .dockerignore +├── .env +├── .gitignore +├── _config.yml +├── CODE_OF_CONDUCT.md +├── CONTRIBUTING.md +├── docker-compose.test.yml +├── docker-compose.yml +├── LICENSE +├── README.md +└── SECURITY.md +``` + +## Table of contents (files) + +- 1. [.dockerignore](#.dockerignore) +- 2. [.env](#.env) +- 3. [.github/ISSUE_TEMPLATE/bug_report.md](#.github-ISSUE_TEMPLATE-bug_report.md) +- 4. [.github/pull_request_template.md](#.github-pull_request_template.md) +- 5. [.github/workflows/deploy-pages.yml](#.github-workflows-deploy-pages.yml) +- 6. [.github/workflows/docker.yml](#.github-workflows-docker.yml) +- 7. [.github/workflows/frontend.yml](#.github-workflows-frontend.yml) +- 8. [.gitignore](#.gitignore) +- 9. [_config.yml](#_config.yml) +- 10. [CODE_OF_CONDUCT.md](#CODE_OF_CONDUCT.md) +- 11. [CONTRIBUTING.md](#CONTRIBUTING.md) +- 12. [docker-compose.test.yml](#docker-compose.test.yml) +- 13. [docker-compose.yml](#docker-compose.yml) +- 14. [frontend/app/.gitignore](#frontend-app-.gitignore) +- 15. [frontend/app/__mocks__/expoConstantsMock.js](#frontend-app-__mocks__-expoConstantsMock.js) +- 16. [frontend/app/__mocks__/expoMock.js](#frontend-app-__mocks__-expoMock.js) +- 17. [frontend/app/__mocks__/expoRouterMock.js](#frontend-app-__mocks__-expoRouterMock.js) +- 18. [frontend/app/__tests__/auth.context.test.tsx](#frontend-app-__tests__-auth.context.test.tsx) +- 19. [frontend/app/__tests__/home.screen.test.tsx](#frontend-app-__tests__-home.screen.test.tsx) +- 20. [frontend/app/__tests__/settingsbar.test.tsx](#frontend-app-__tests__-settingsbar.test.tsx) +- 21. [frontend/app/__tests__/smoke.test.tsx](#frontend-app-__tests__-smoke.test.tsx) +- 22. [frontend/app/app.json](#frontend-app-app.json) +- 23. [frontend/app/App.tsx](#frontend-app-App.tsx) +- 24. [frontend/app/babel.config.js](#frontend-app-babel.config.js) +- 25. [frontend/app/components/SettingsBar.tsx](#frontend-app-components-SettingsBar.tsx) +- 26. [frontend/app/context/Auth.tsx](#frontend-app-context-Auth.tsx) +- 27. [frontend/app/index.ts](#frontend-app-index.ts) +- 28. [frontend/app/jest.config.ts](#frontend-app-jest.config.ts) +- 29. [frontend/app/jest.setup.ts](#frontend-app-jest.setup.ts) +- 30. [frontend/app/jest.setupFiles.js](#frontend-app-jest.setupFiles.js) +- 31. [frontend/app/metro.config.js](#frontend-app-metro.config.js) +- 32. [frontend/app/public/404.html](#frontend-app-public-404.html) +- 33. [frontend/app/public/google095bf08db4fb15d0.html](#frontend-app-public-google095bf08db4fb15d0.html) +- 34. [frontend/app/screens/HomeScreen.tsx](#frontend-app-screens-HomeScreen.tsx) +- 35. [frontend/app/tsconfig.json](#frontend-app-tsconfig.json) +- 36. [frontend/Dockerfile](#frontend-Dockerfile) +- 37. [frontend/Dockerfile.test](#frontend-Dockerfile.test) +- 38. [LICENSE](#LICENSE) +- 39. [README.md](#README.md) +- 40. [SECURITY.md](#SECURITY.md) + +--- + +## Files + + +### 1. `.dockerignore` +- Size: 305 bytes | LOC: 34 | SLOC: 29 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 4ddfad028bab + +#### Brief +# Git +.git + +#### Auto Summary +# Git + +#### Content + +``` +# Git +.git +.gitignore + +# Python cache +__pycache__/ +*.py[cod] +*.pyo +*.pyd + +# Node +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Local env/config +.env +*.env +*.local + +# OS / IDE +.DS_Store +Thumbs.db +desktop.ini +.vscode/ +.idea/ + +# Build artifacts +dist/ +build/ +web-build/ +.expo/ +.expo-shared/ +``` + + +### 2. `.env` +- Size: 230 bytes | LOC: 6 | SLOC: 6 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: ee13beb2355e + +#### Brief +# Frontend (Expo) +EXPO_TUNNEL=false + +#### Auto Summary +# Frontend (Expo) + +#### Content + +``` +# Frontend (Expo) +EXPO_TUNNEL=false +EXPO_DEVTOOLS_LISTEN_ADDRESS=0.0.0.0 +EXPO_PUBLIC_API_HOST=${REACT_NATIVE_PACKAGER_HOSTNAME} +EXPO_PUBLIC_API_PORT=8000 +EXPO_PUBLIC_API_BASE=http://${EXPO_PUBLIC_API_HOST}:${EXPO_PUBLIC_API_PORT} +``` + + +### 3. `.github/ISSUE_TEMPLATE/bug_report.md` +- Size: 666 bytes | LOC: 30 | SLOC: 24 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 4b4d2b96744a + +#### Brief +--- +name: Bug Report + +#### Auto Summary +Description + +#### Content (verbatim) + +```markdown +--- +name: Bug Report +about: Create a report to help us improve +title: "[Bug] " +labels: bug +assignees: '' +--- + +## Description + + +## Steps to Reproduce +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +## Expected Behavior + + +## Screenshots + + +## Environment +- OS: [e.g. Ubuntu 22.04] +- Browser/Version: [e.g. Chrome 117] +- Node/Python Version: [e.g. Node 18, Python 3.10] + +## Additional Context + +``` + + +### 4. `.github/pull_request_template.md` +- Size: 744 bytes | LOC: 27 | SLOC: 21 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 1b6e1aab4d9a + +#### Brief +# Pull Request + +#### Auto Summary +Pull Request + +#### Content (verbatim) + +```markdown +# Pull Request + +## Overview + + +## Changes + +- + +## Testing + +- [ ] Built and ran locally without errors +- [ ] All tests passed +- [ ] Verified functionality manually (describe how) + +## Related Issues + +- Closes # + +## Checklist +- [ ] Code is clean and free of unnecessary comments/debug prints +- [ ] Proper naming conventions and documentation are followed +- [ ] Updated documentation/README if necessary +- [ ] CI pipeline passes successfully + +## Notes for Reviewers + +``` + + +### 5. `.github/workflows/deploy-pages.yml` +- Size: 1203 bytes | LOC: 47 | SLOC: 47 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: d8ecc8534c1d + +#### Brief +name: Deploy Expo Web to GitHub Pages +on: + +#### Auto Summary +name: Deploy Expo Web to GitHub Pages + +#### Content + +```yaml +name: Deploy Expo Web to GitHub Pages +on: + push: + branches: [ "main" ] + workflow_dispatch: +permissions: + contents: read + pages: write + id-token: write +concurrency: + group: "pages" + cancel-in-progress: true +jobs: + build: + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Use Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: | + frontend/app/package-lock.json + - name: Install deps + working-directory: frontend/app + run: npm ci + - name: Build (expo export -p web) + working-directory: frontend/app + run: npx expo export -p web + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: frontend/app/dist + deploy: + if: github.ref == 'refs/heads/main' + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 +``` + + +### 6. `.github/workflows/docker.yml` +- Size: 807 bytes | LOC: 27 | SLOC: 27 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 8370695b3ad1 + +#### Brief +name: Frontend Tests via Docker +on: + +#### Auto Summary +name: Frontend Tests via Docker + +#### Content + +```yaml +name: Frontend Tests via Docker +on: + workflow_dispatch: + pull_request: +jobs: + docker-tests: + runs-on: ubuntu-latest + env: + CI: "1" + EXPO_TUNNEL: "false" + EXPO_DEVTOOLS_LISTEN_ADDRESS: 0.0.0.0 + EXPO_PUBLIC_API_BASE: http://localhost:8000 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Run tests via docker-compose (deterministic CI) + run: | + docker compose -f docker-compose.test.yml up --build --exit-code-from frontend_test + - name: Upload test reports (if mounted) + if: always() + uses: actions/upload-artifact@v4 + with: + name: frontend-reports + path: reports/frontend + if-no-files-found: warn +``` + + +### 7. `.github/workflows/frontend.yml` +- Size: 2348 bytes | LOC: 94 | SLOC: 78 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 7988b4c569e4 + +#### Brief +name: Frontend CI + +#### Auto Summary +name: Frontend CI + +#### Content + +```yaml +name: Frontend CI + +on: + push: + pull_request: + +concurrency: + group: frontend-ci-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + test: + name: Jest on Node ${{ matrix.node }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node: [18, 20] # LTS + current used in repo + + env: + CI: "1" + EXPO_TUNNEL: "false" + EXPO_DEVTOOLS_LISTEN_ADDRESS: 0.0.0.0 + EXPO_PUBLIC_API_BASE: http://localhost:8000 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Use Node ${{ matrix.node }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: npm + cache-dependency-path: frontend/app/package-lock.json + + - name: Install deps + working-directory: frontend/app + run: npm ci + + - name: Type check (skip if no TS) + if: hashFiles('frontend/app/tsconfig.json') != '' + working-directory: frontend/app + run: | + npx --yes typescript@latest -v >/dev/null 2>&1 || true + npx tsc --noEmit || (echo "::warning::Type check failed"; exit 1) + + - name: Run Jest with coverage (CI mode) + working-directory: frontend/app + run: npm test -- --ci --runInBand --coverage --verbose + + - name: Upload coverage + if: always() + uses: actions/upload-artifact@v4 + with: + name: coverage-node${{ matrix.node }} + path: frontend/app/coverage + if-no-files-found: warn + + web-export-smoke: + name: Expo web export (smoke) + needs: test + runs-on: ubuntu-latest + env: + CI: "1" + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Use Node 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: frontend/app/package-lock.json + + - name: Install deps + working-directory: frontend/app + run: npm ci + + - name: Build (expo export -p web) + working-directory: frontend/app + run: npx expo export -p web + + - name: Upload dist (preview artifact) + if: always() + uses: actions/upload-artifact@v4 + with: + name: expo-web-dist + path: frontend/app/dist + if-no-files-found: error +``` + + +### 8. `.gitignore` +- Size: 585 bytes | LOC: 58 | SLOC: 49 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 7b43f8e8f583 + +#### Brief +# Python +__pycache__/ + +#### Auto Summary +# Python + +#### Content + +``` +# Python +__pycache__/ +*.py[cod] +*.pyo +*.pyd +*.so +*.egg +*.egg-info/ +.eggs/ +*.log +*.sqlite3 +db.sqlite3 + +# Virtual env +.venv/ +env/ +venv/ +ENV/ + +# Jupyter +.ipynb_checkpoints/ + +# Node / React Native (Expo) +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.expo/ +.expo-shared/ +web-build/ +dist/ +build/ + +# macOS / Linux / Windows +.DS_Store +Thumbs.db +desktop.ini + +# IDE +.vscode/ +.idea/ +*.swp + +# Docker +*.pid +*.sock +*.tar + +# coverage reports (backend & frontend) +coverage/ +backend/app/coverage/ +frontend/app/coverage/ + +# Jest cache +frontend/app/.jest-cache/ + +make_md.py +python_front_* +``` + + +### 9. `_config.yml` +- Size: 217 bytes | LOC: 10 | SLOC: 10 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: f87ea6343cf3 + +#### Brief +title: "python_front" +description: "A playground for Python" + +#### Auto Summary +title: "python_front" + +#### Content + +```yaml +title: "python_front" +description: "A playground for Python" +baseurl: "/python_front" +url: "https://europanite.github.io" +theme: minima +markdown: kramdown +plugins: + - jekyll-feed + - jekyll-sitemap +highlighter: rouge +``` + + +### 10. `CODE_OF_CONDUCT.md` +- Size: 4085 bytes | LOC: 68 | SLOC: 50 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 724cc33f0dca + +#### Brief +# Contributor Covenant Code of Conduct + +#### Auto Summary +Contributor Covenant Code of Conduct + +#### Content (verbatim) + +```markdown +# Contributor Covenant Code of Conduct + +## Our Pledge +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards +Examples of behavior that contributes to a positive environment include: +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Taking responsibility and apologizing to those affected by our mistakes +- Focusing on what is best for the community, not just for ourselves + +Examples of unacceptable behavior include: +- The use of sexualized language or imagery, and sexual attention or advances +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others’ private information without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +## Scope +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. + +## Enforcement +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the maintainers of this project. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines +Community leaders will follow these guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +1. **Correction** +- *Impact*: Use of inappropriate language or other behavior deemed unprofessional. +- *Consequence*: A private, written warning, with clarity around the nature of the violation and an explanation of why the behavior was inappropriate. + +2. **Warning** +- *Impact*: A violation through a single incident or series of actions. +- *Consequence*: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. + +3. **Temporary Ban** +- *Impact*: A serious violation of community standards, including sustained inappropriate behavior. +- *Consequence*: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. + +4. **Permanent Ban** +- *Impact*: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. +- *Consequence*: A permanent ban from any sort of public interaction within the + community. + +## Attribution + +This Code of Conduct is adapted from the +- [Contributor Covenant][v2.1], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +- [Mozilla’s code of conduct enforcement ladder][mozilla-coc]. + +For answers to common questions about this code of conduct, see the FAQ at +- [Contributor Covenant FAQ][faq]. + +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[mozilla-coc]: https://github.com/mozilla/diversity +[faq]: https://www.contributor-covenant.org/faq +``` + + +### 11. `CONTRIBUTING.md` +- Size: 834 bytes | LOC: 25 | SLOC: 19 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 12ea21d94090 + +#### Brief +# Contributing Guidelines + +#### Auto Summary +Contributing Guidelines + +#### Content (verbatim) + +```markdown +# Contributing Guidelines + +Thank you for considering contributing to this project! +We welcome bug reports, feature requests, and pull requests. +Please follow the guidelines below to make the process smooth for everyone. + +--- + +## How to Contribute + +### 1. Reporting Issues +- Check the [issue tracker](../../issues) to avoid duplicates. +- Use the appropriate [issue template](.github/ISSUE_TEMPLATE/) when creating a new issue. +- Provide as much detail as possible (steps to reproduce, expected behavior, environment, etc.). + +### 2. Suggesting Features +- Open a new **Feature Request** issue. +- Explain the problem your idea solves. +- Provide examples, use cases, or references if possible. + +### 3. Submitting Pull Requests +Fork the repository and create a new branch: + ```bash + git checkout -b feature/your-feature-name + ``` +``` + + +### 12. `docker-compose.test.yml` +- Size: 706 bytes | LOC: 21 | SLOC: 21 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: ae51e77ed333 + +#### Brief +services: + frontend_test: + +#### Auto Summary +services: + +#### Content + +```yaml +services: + frontend_test: + container_name: frontend_test + build: + context: ./frontend + dockerfile: Dockerfile.test + working_dir: /app + environment: + CI: "1" + EXPO_TUNNEL: ${EXPO_TUNNEL} + EXPO_DEVTOOLS_LISTEN_ADDRESS: ${EXPO_DEVTOOLS_LISTEN_ADDRESS} + REACT_NATIVE_PACKAGER_HOSTNAME: ${REACT_NATIVE_PACKAGER_HOSTNAME} + EXPO_PUBLIC_API_HOST: ${EXPO_PUBLIC_API_HOST} + EXPO_PUBLIC_API_PORT: ${EXPO_PUBLIC_API_PORT} + EXPO_PUBLIC_API_BASE: ${EXPO_PUBLIC_API_BASE} + volumes: + - ./frontend/app:/app + - /app/node_modules + - ./reports/frontend:/reports/frontend + command: >- + sh -lc "npm ci && npm test -- --ci --runInBand --verbose" +``` + + +### 13. `docker-compose.yml` +- Size: 887 bytes | LOC: 30 | SLOC: 30 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 6798f1d1af83 + +#### Brief +services: + frontend: + +#### Auto Summary +services: + +#### Content + +```yaml +services: + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + container_name: frontend + working_dir: /app + stdin_open: true + tty: true + restart: unless-stopped + volumes: + - ./frontend/app:/app + - /app/node_modules + environment: + EXPO_TUNNEL: ${EXPO_TUNNEL} + EXPO_DEVTOOLS_LISTEN_ADDRESS: ${EXPO_DEVTOOLS_LISTEN_ADDRESS} + REACT_NATIVE_PACKAGER_HOSTNAME: ${REACT_NATIVE_PACKAGER_HOSTNAME} + EXPO_PUBLIC_API_HOST: ${EXPO_PUBLIC_API_HOST} + EXPO_PUBLIC_API_PORT: ${EXPO_PUBLIC_API_PORT} + EXPO_PUBLIC_API_BASE: ${EXPO_PUBLIC_API_BASE} + ports: + - "19000:19000" # Expo dev server (Metro) + - "19001:19001" # React Native debugger(old) + - "19002:19002" # Expo web UI + - "8081:8081" # Metro bundler + command: > + sh -c " + npm install && + npx expo start + " +``` + + +### 14. `frontend/app/.gitignore` +- Size: 398 bytes | LOC: 37 | SLOC: 29 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 87b612309282 + +#### Brief +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +#### Auto Summary +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +#### Content + +``` +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +# dependencies +node_modules/ + +# Expo +.expo/ +dist/ +web-build/ +expo-env.d.ts + +# Native +.kotlin/ +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +# Metro +.metro-health-check* + +# debug +npm-debug.* +yarn-debug.* +yarn-error.* + +# macOS +.DS_Store +*.pem + +# local env files +.env*.local + +# typescript +*.tsbuildinfo +``` + + +### 15. `frontend/app/__mocks__/expoConstantsMock.js` +- Size: 97 bytes | LOC: 3 | SLOC: 3 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 042b2b318b7a + +#### Brief +module.exports = { + default: { expoConfig: {}, manifest: null, appOwnership: 'standalone' }, + +#### Auto Summary +module.exports = { + +#### Content + +```javascript +module.exports = { + default: { expoConfig: {}, manifest: null, appOwnership: 'standalone' }, +}; +``` + + +### 16. `frontend/app/__mocks__/expoMock.js` +- Size: 174 bytes | LOC: 7 | SLOC: 7 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: d58b65ebbe06 + +#### Brief +const dummy = new Proxy({}, { get: () => undefined }); +module.exports = { + +#### Auto Summary +const dummy = new Proxy({}, { get: () => undefined }); + +#### Content + +```javascript +const dummy = new Proxy({}, { get: () => undefined }); +module.exports = { + ...dummy, + registerRootComponent: () => {}, + installExpoGlobals: () => {}, + default: dummy, +}; +``` + + +### 17. `frontend/app/__mocks__/expoRouterMock.js` +- Size: 179 bytes | LOC: 7 | SLOC: 7 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: bec4abda4523 + +#### Brief +module.exports = { + useRouter: () => ({ push: () => {}, replace: () => {}, back: () => {} }), + +#### Auto Summary +module.exports = { + +#### Content + +```javascript +module.exports = { + useRouter: () => ({ push: () => {}, replace: () => {}, back: () => {} }), + useSegments: () => [], + Slot: () => null, + Stack: () => null, + default: {}, +}; +``` + + +### 18. `frontend/app/__tests__/auth.context.test.tsx` +- Size: 879 bytes | LOC: 32 | SLOC: 27 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 8b991e2bc175 + +#### Brief +import React, { useEffect } from 'react'; +import { Text } from 'react-native'; + +#### Auto Summary +import React, { useEffect } from 'react'; + +#### Content + +```tsx +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'); +}); +``` + + +### 19. `frontend/app/__tests__/home.screen.test.tsx` +- Size: 1421 bytes | LOC: 37 | SLOC: 24 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: da2189feb3a1 + +#### Brief +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +#### Auto Summary +import React from 'react'; + +#### Content + +```tsx +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +// Mock pyodide to avoid network and heavy init +jest.mock('pyodide', () => ({ + loadPyodide: jest.fn().mockResolvedValue({ + 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) } }); + +// 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() }) }; +}); + +// Mock useAuth to a simple anonymous state +jest.mock('../context/Auth', () => ({ useAuth: jest.fn(() => ({ user: null })) })); + +const HomeScreen = require('../screens/HomeScreen').default; + +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(); +}); +``` + + +### 20. `frontend/app/__tests__/settingsbar.test.tsx` +- Size: 892 bytes | LOC: 23 | SLOC: 16 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 1bac9d470083 + +#### Brief +import React from 'react'; +import { render, screen } from '@testing-library/react-native'; + +#### Auto Summary +import React from 'react'; + +#### Content + +```tsx +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); +}); +``` + + +### 21. `frontend/app/__tests__/smoke.test.tsx` +- Size: 288 bytes | LOC: 10 | SLOC: 8 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 29a77aa1510f + +#### Brief +import React from 'react'; +import { Text } from 'react-native'; + +#### Auto Summary +import React from 'react'; + +#### Content + +```tsx +import React from 'react'; +import { Text } from 'react-native'; +import { render, screen } from '@testing-library/react-native'; + +function Hello(){ return hi; } + +test('smoke', async () => { + render(); + expect(screen.getByTestId('hi')).toBeTruthy(); +}); +``` + + +### 22. `frontend/app/app.json` +- Size: 745 bytes | LOC: 34 | SLOC: 34 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 348660964aeb + +#### Brief +{ + "expo": { + +#### Auto Summary +{ + +#### Content + +```json +{ + "expo": { + "name": "app", + "slug": "app", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/icon.png", + "userInterfaceStyle": "light", + "newArchEnabled": true, + "splash": { + "image": "./assets/splash-icon.png", + "resizeMode": "contain", + "backgroundColor": "#ffffff" + }, + "ios": { + "supportsTablet": true + }, + "android": { + "adaptiveIcon": { + "foregroundImage": "./assets/adaptive-icon.png", + "backgroundColor": "#ffffff" + }, + "edgeToEdgeEnabled": true + }, + "experiments": { + "baseUrl": "/python_front" + }, + "web": { + "favicon": "./assets/favicon.png", + "output": "single", + "bundler": "metro" + } + } +} +``` + + +### 23. `frontend/app/App.tsx` +- Size: 991 bytes | LOC: 30 | SLOC: 27 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: c327cd2f2101 + +#### Brief +import React from "react"; +import { View } from "react-native"; + +#### Auto Summary +import React from "react"; + +#### Content + +```tsx +import React from "react"; +import { View } from "react-native"; +import { NavigationContainer } from "@react-navigation/native"; +import { createNativeStackNavigator } from "@react-navigation/native-stack"; +import { SafeAreaProvider } from "react-native-safe-area-context"; +import { StatusBar } from "expo-status-bar"; + +import { AuthProvider } from "./context/Auth"; +import SettingsBar from "./components/SettingsBar"; +import HomeScreen from "./screens/HomeScreen"; + +const Stack = createNativeStackNavigator(); + +export default function App() { + return ( + + + + + + + + + + + + + + ); +} +``` + + +### 24. `frontend/app/babel.config.js` +- Size: 294 bytes | LOC: 15 | SLOC: 15 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: d5c5f238e459 + +#### Brief +module.exports = function (api) { + api.cache(true); + +#### Auto Summary +module.exports = function (api) { + +#### Content + +```javascript +module.exports = function (api) { + api.cache(true); + return { + presets: ['babel-preset-expo'], + env: { + test: { + plugins: [ + ['transform-inline-environment-variables', { + include: ['EXPO_PUBLIC_API_BASE'] + }], + ], + }, + }, + }; +}; +``` + + +### 25. `frontend/app/components/SettingsBar.tsx` +- Size: 1656 bytes | LOC: 51 | SLOC: 47 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 923ce657afe8 + +#### Brief +import React from "react"; +import { View, Text, TouchableOpacity, useWindowDimensions } from "react-native"; + +#### Auto Summary +import React from "react"; + +#### Content + +```tsx +import React from "react"; +import { View, Text, TouchableOpacity, useWindowDimensions } from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { StatusBar } from "expo-status-bar"; +import { useAuth } from "../context/Auth"; +import { useNavigation } from "@react-navigation/native"; + +const BAR_BG = "#000000ff"; +const CONTENT_MAX_W = 480; // ← same as forms + +export default function SettingsBar() { + const nav = useNavigation(); + const { width } = useWindowDimensions(); + const isNarrow = width < 420; // stack buttons below on very small widths + + const Btn = ({ title, onPress }: { title: string; onPress: () => void }) => ( + + {title} + + ); + + return ( + + + + + + + + + + + + ); +} +``` + + +### 26. `frontend/app/context/Auth.tsx` +- Size: 1001 bytes | LOC: 35 | SLOC: 25 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 8a2139845413 + +#### Brief +Simple auth context that stores token/email in memory. +You can later persist it with AsyncStorage if needed. + +#### Auto Summary +// Simple auth context that stores token/email in memory. + +#### Content + +```tsx +// Simple auth context that stores token/email in memory. +// You can later persist it with AsyncStorage if needed. +import React, { createContext, useContext, useState, ReactNode } from "react"; + +type User = { email: string } | null; + +type AuthCtx = { + user: User; + token: string | null; + authHeader: () => Partial>; +}; + +const AuthContext = createContext(null); + +const API_BASE = process.env.EXPO_PUBLIC_API_BASE!; + +export function AuthProvider({ children }: { children: ReactNode }) { + const [token, setToken] = useState(null); + const [user, setUser] = useState(null); + + const authHeader = () => + token ? { Authorization: `Bearer ${token}` } : {}; + + return ( + + {children} + + ); +} + +export function useAuth() { + const ctx = useContext(AuthContext); + if (!ctx) throw new Error("useAuth must be used within AuthProvider"); + return ctx; +} +``` + + +### 27. `frontend/app/index.ts` +- Size: 307 bytes | LOC: 8 | SLOC: 3 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: fd1e69b3b668 + +#### Brief +import { registerRootComponent } from 'expo'; + +#### Auto Summary +import { registerRootComponent } from 'expo'; + +#### Content + +```typescript +import { registerRootComponent } from 'expo'; + +import App from './App'; + +// registerRootComponent calls AppRegistry.registerComponent('main', () => App); +// It also ensures that whether you load the app in Expo Go or in a native build, +// the environment is set up appropriately +registerRootComponent(App); +``` + + +### 28. `frontend/app/jest.config.ts` +- Size: 1011 bytes | LOC: 29 | SLOC: 29 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 6aa9c9ed7996 + +#### Brief +module.exports = { + preset: 'jest-expo', + +#### Auto Summary +module.exports = { + +#### Content + +```typescript + module.exports = { + preset: 'jest-expo', + testEnvironment: 'jsdom', + setupFilesAfterEnv: ['/jest.setup.ts'], + moduleNameMapper: { + '\\.(png|jpe?g|gif|svg)$': '/__mocks__/fileMock.js', + '\\.(css|scss)$': '/__mocks__/styleMock.js', + 'react-native-reanimated': 'react-native-reanimated/mock', + '^expo($|/.+)': '/__mocks__/expoMock.js', + '^expo-router($|/.+)': '/__mocks__/expoRouterMock.js', + '^expo-constants$': '/__mocks__/expoConstantsMock.js', + }, + setupFiles: ['/jest.setupFiles.js'], + transformIgnorePatterns: [ + 'node_modules/(?!(react-native' + + '|@react-native' + + '|react-clone-referenced-element' + + '|@react-navigation' + + '|react-navigation' + + '|expo(nent)?' + + '|@expo(nent)?/.*' + + '|expo-modules-core' + + '|expo-.*' + + '|@expo/.*' + + '|@react-native/polyfills' + + ')/)', + ], + testMatch: ['**/__tests__/**/*.test.(ts|tsx)'], + }; +``` + + +### 29. `frontend/app/jest.setup.ts` +- Size: 88 bytes | LOC: 2 | SLOC: 2 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: f764402eb556 + +#### Brief +import '@testing-library/jest-native/extend-expect'; +import '@testing-library/jest-dom'; + +#### Auto Summary +import '@testing-library/jest-native/extend-expect'; + +#### Content + +```typescript +import '@testing-library/jest-native/extend-expect'; +import '@testing-library/jest-dom'; +``` + + +### 30. `frontend/app/jest.setupFiles.js` +- Size: 395 bytes | LOC: 9 | SLOC: 8 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 8572a798e340 + +#### Brief +if (!Object.getOwnPropertyDescriptor(globalThis, '__ExpoImportMetaRegistry')) { + Object.defineProperty(globalThis, '__ExpoImportMetaRegistry', { + +#### Auto Summary +if (!Object.getOwnPropertyDescriptor(globalThis, '__ExpoImportMetaRegistry')) { + +#### Content + +```javascript +if (!Object.getOwnPropertyDescriptor(globalThis, '__ExpoImportMetaRegistry')) { + Object.defineProperty(globalThis, '__ExpoImportMetaRegistry', { + configurable: true, + value: { get: () => null, has: () => false }, + }); +} + +jest.mock('@react-native/animated', () => ({}), { virtual: true }); +jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper', () => ({}), { virtual: true }); +``` + + +### 31. `frontend/app/metro.config.js` +- Size: 128 bytes | LOC: 3 | SLOC: 3 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 2c699d1e4a93 + +#### Brief +const { getDefaultConfig } = require('expo/metro-config'); +const config = getDefaultConfig(__dirname); + +#### Auto Summary +const { getDefaultConfig } = require('expo/metro-config'); + +#### Content + +```javascript +const { getDefaultConfig } = require('expo/metro-config'); +const config = getDefaultConfig(__dirname); +module.exports = config; +``` + + +### 32. `frontend/app/public/404.html` +- Size: 464 bytes | LOC: 12 | SLOC: 12 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: c9595010e927 + +#### Brief + + Redirecting... + +#### Auto Summary + + +#### Content + +```html + + Redirecting... + +Redirecting... +``` + + +### 33. `frontend/app/public/google095bf08db4fb15d0.html` +- Size: 53 bytes | LOC: 1 | SLOC: 1 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: 6d7f2ccca405 + +#### Brief +google-site-verification: google095bf08db4fb15d0.html + +#### Auto Summary +google-site-verification: google095bf08db4fb15d0.html + +#### Content + +```html +google-site-verification: google095bf08db4fb15d0.html +``` + + +### 34. `frontend/app/screens/HomeScreen.tsx` +- Size: 8293 bytes | LOC: 271 | SLOC: 242 | TODOs: 0 | Modified: 2025-10-19 20:28:35 | SHA1: e7811d526f20 + +#### Brief +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { useWindowDimensions } from "react-native"; + +#### Auto Summary +import React, { useEffect, useMemo, useRef, useState } from "react"; + +#### Content + +```tsx +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { useWindowDimensions } from "react-native"; +import Button from "@mui/material/Button"; +import Textarea from "@mui/joy/Textarea"; +import CircularProgress from "@mui/material/CircularProgress"; +import Stack from "@mui/material/Stack"; +import Chip from "@mui/material/Chip"; +import Divider from "@mui/material/Divider"; +import Paper from "@mui/material/Paper"; +import Tooltip from "@mui/material/Tooltip"; +import { loadPyodide, PyodideInterface } from "pyodide"; + +/** + * HomeScreen (Vertical layout, toolbar on top) + * --------------------------------------------- + * [ Top Toolbar: Title + Buttons + Status ] + * [ Code Window (flex: 1) ] + * [ Console Window (short, fixed height) ] + */ + +type ExecResult = + | { isError?: false; text: string } + | { isError: true; text: string }; + +const DEFAULT_SAMPLE = `# Sample: +# print number +a = 1 +b = 2 +c = a + b +print(c) +`; + +export default function HomeScreen() { + const [pyodide, setPyodide] = useState(null); + const [status, setStatus] = useState<"idle" | "loading" | "ready" | "running">("loading"); + const [code, setCode] = useState(DEFAULT_SAMPLE); + const [consoleText, setConsoleText] = useState(""); + const consoleRef = useRef(null); + + // soft-cancel token for "Stop" + const execIdRef = useRef(0); + + const { height, width } = useWindowDimensions(); + // Keep console clearly smaller than before (≈ 20% of viewport, clamped) + const consoleHeight = Math.max(120, Math.min(200, Math.round(height * 0.22))); + const narrow = width < 720; + + const styles = useMemo(() => { + return { + root: { + width: "100%", + maxWidth: 1100, + margin: "0 auto", + padding: 12, + display: "flex", + flexDirection: "column" as const, + gap: 12, + minHeight: height - 60, + }, + toolbar: { + display: "flex", + alignItems: "center", + justifyContent: "space-between", + gap: 12, + flexWrap: "wrap" as const, + }, + title: { fontSize: 22, fontWeight: 800, margin: 0 }, + codePaper: { + flex: 1, + display: "flex", + flexDirection: "column" as const, + gap: 8, + padding: 12, + minHeight: 260, + }, + codeTextarea: { + fontFamily: "ui-monospace, Menlo, Monaco, Consolas, monospace", + fontSize: 14, + minHeight: 220, + flex: 1, + }, + consolePaper: { + padding: 12, + display: "flex", + flexDirection: "column" as const, + gap: 8, + }, + consoleBox: { + height: consoleHeight, + background: "#0b0b0b", + color: "#e6e6e6", + borderRadius: 8, + padding: 12, + overflow: "auto" as const, + fontFamily: "ui-monospace, Menlo, Monaco, Consolas, monospace", + fontSize: 14, + border: "1px solid #333", + lineHeight: 1.35, + whiteSpace: "pre-wrap" as const, + }, + }; + }, [height, width, consoleHeight]); + + // auto-scroll console + useEffect(() => { + if (consoleRef.current) consoleRef.current.scrollTop = consoleRef.current.scrollHeight; + }, [consoleText]); + + // load Pyodide once + useEffect(() => { + (async () => { + try { + setStatus("loading"); + const instance = await loadPyodide({ + indexURL: "https://cdn.jsdelivr.net/pyodide/v0.23.0/full/", + }); + setPyodide(instance); + setStatus("ready"); + } catch (e) { + appendConsole({ isError: true, text: toErrMsg(e) }); + setStatus("idle"); + } + })(); + }, []); + + function toErrMsg(err: any) { + return err instanceof Error ? err.message : String(err); + } + function appendConsole(res: ExecResult) { + setConsoleText((prev) => { + const line = res.isError ? `[ERROR] ${res.text}` : res.text; + return prev ? `${prev}\n${line}` : line; + }); + } + function clearConsole() { + setConsoleText(""); + } + function loadSample() { + setCode(DEFAULT_SAMPLE); + } + async function copyOutput() { + try { + await navigator.clipboard.writeText(consoleText); + appendConsole({ text: "[INFO] Output copied to clipboard." }); + } catch { + appendConsole({ isError: true, text: "Failed to copy output." }); + } + } + + async function run() { + if (!pyodide || status !== "ready") return; + setStatus("running"); + const myExecId = ++execIdRef.current; + + try { + // capture streams + const lines: string[] = []; + pyodide.setStdout({ batched: (t: string) => lines.push(t) }); + pyodide.setStderr({ batched: (t: string) => lines.push(t) }); + + // soft timeout guard + const TIMEOUT_MS = 30_000; + const p = pyodide.runPythonAsync(code); + await Promise.race([p, new Promise((_, rej) => setTimeout(() => rej(new Error("Execution timeout")), TIMEOUT_MS))]); + + // if not stopped mid-run, flush + if (myExecId === execIdRef.current) { + const out = lines.join(""); + appendConsole({ text: out || "(no output)" }); + } + } catch (e) { + if (myExecId === execIdRef.current) appendConsole({ isError: true, text: toErrMsg(e) }); + } finally { + if (myExecId === execIdRef.current) setStatus("ready"); + } + } + + function stop() { + // Soft-cancel: bump token so any in-flight run discards its result + execIdRef.current++; + appendConsole({ isError: true, text: "Stopped current execution." }); + // Note: For true hard-cancel, move execution to a WebWorker and terminate it. + } + + const statusChip = ( + : undefined} + sx={{ fontWeight: 700 }} + /> + ); + + return ( +
+ {/* Top toolbar: Title + Buttons + Status */} +
+

+ + Python Front + +

+ + + + + + + + + + + + + {statusChip} +
+ + {/* Code window (big, flexible) */} + + Code + +