python2mobile 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. examples/example_ecommerce_app.py +189 -0
  2. examples/example_todo_app.py +159 -0
  3. p2m/__init__.py +31 -0
  4. p2m/cli.py +470 -0
  5. p2m/config.py +205 -0
  6. p2m/core/__init__.py +18 -0
  7. p2m/core/api.py +191 -0
  8. p2m/core/ast_walker.py +171 -0
  9. p2m/core/database.py +192 -0
  10. p2m/core/events.py +56 -0
  11. p2m/core/render_engine.py +597 -0
  12. p2m/core/runtime.py +128 -0
  13. p2m/core/state.py +51 -0
  14. p2m/core/validator.py +284 -0
  15. p2m/devserver/__init__.py +9 -0
  16. p2m/devserver/server.py +84 -0
  17. p2m/i18n/__init__.py +7 -0
  18. p2m/i18n/translator.py +74 -0
  19. p2m/imagine/__init__.py +35 -0
  20. p2m/imagine/agent.py +463 -0
  21. p2m/imagine/legacy.py +217 -0
  22. p2m/llm/__init__.py +20 -0
  23. p2m/llm/anthropic_provider.py +78 -0
  24. p2m/llm/base.py +42 -0
  25. p2m/llm/compatible_provider.py +120 -0
  26. p2m/llm/factory.py +72 -0
  27. p2m/llm/ollama_provider.py +89 -0
  28. p2m/llm/openai_provider.py +79 -0
  29. p2m/testing/__init__.py +41 -0
  30. p2m/ui/__init__.py +43 -0
  31. p2m/ui/components.py +301 -0
  32. python2mobile-1.0.1.dist-info/METADATA +238 -0
  33. python2mobile-1.0.1.dist-info/RECORD +50 -0
  34. python2mobile-1.0.1.dist-info/WHEEL +5 -0
  35. python2mobile-1.0.1.dist-info/entry_points.txt +2 -0
  36. python2mobile-1.0.1.dist-info/top_level.txt +3 -0
  37. tests/test_basic_engine.py +281 -0
  38. tests/test_build_generation.py +603 -0
  39. tests/test_build_test_gate.py +150 -0
  40. tests/test_carousel_modal.py +84 -0
  41. tests/test_config_system.py +272 -0
  42. tests/test_i18n.py +101 -0
  43. tests/test_ifood_app_integration.py +172 -0
  44. tests/test_imagine_cli.py +133 -0
  45. tests/test_imagine_command.py +341 -0
  46. tests/test_llm_providers.py +321 -0
  47. tests/test_new_apps_integration.py +588 -0
  48. tests/test_ollama_functional.py +329 -0
  49. tests/test_real_world_apps.py +228 -0
  50. tests/test_run_integration.py +776 -0
@@ -0,0 +1,172 @@
1
+ """
2
+ Integration tests for tests-p2m/ifood_app.
3
+
4
+ Uses the same module-isolation strategy as test_new_apps_integration.py.
5
+ """
6
+
7
+ import sys
8
+ import importlib
9
+ from pathlib import Path
10
+ import pytest
11
+
12
+ # ── Paths ─────────────────────────────────────────────────────────────────────
13
+ FRAMEWORK_PATH = Path(__file__).parent.parent # .../python2mobile/
14
+ TESTS_P2M_PATH = FRAMEWORK_PATH.parent / "tests-p2m" # .../tests-p2m/
15
+ IFOOD_DIR = TESTS_P2M_PATH / "ifood_app"
16
+
17
+ if str(FRAMEWORK_PATH) not in sys.path:
18
+ sys.path.insert(0, str(FRAMEWORK_PATH))
19
+
20
+ from p2m.core import events, Render
21
+ from p2m.core.render_engine import RenderEngine
22
+
23
+
24
+ # ── Helpers ───────────────────────────────────────────────────────────────────
25
+
26
+ def _render(create_view) -> str:
27
+ tree = Render.execute(create_view)
28
+ return RenderEngine().render_content(tree)
29
+
30
+
31
+ def _load_app():
32
+ sys.modules.pop("main", None)
33
+ return importlib.import_module("main")
34
+
35
+
36
+ # ── Fixture ───────────────────────────────────────────────────────────────────
37
+
38
+ @pytest.fixture()
39
+ def ifood_app():
40
+ saved_path = list(sys.path)
41
+ saved_module_keys = set(sys.modules.keys())
42
+ events.clear()
43
+
44
+ # Reset i18n state before each test
45
+ import p2m.i18n.translator as _t
46
+ _t._locale = "en"
47
+ _t._translations = {}
48
+ _t._locales_dir = None
49
+
50
+ sys.path.insert(0, str(IFOOD_DIR))
51
+ mod = _load_app()
52
+ _render(mod.create_view) # initial render registers event handlers
53
+
54
+ from state.store import store
55
+ yield mod, store
56
+
57
+ for key in list(sys.modules.keys()):
58
+ if key not in saved_module_keys:
59
+ del sys.modules[key]
60
+ sys.path[:] = saved_path
61
+ events.clear()
62
+
63
+ # Reset i18n again after test
64
+ _t._locale = "en"
65
+ _t._translations = {}
66
+ _t._locales_dir = None
67
+
68
+
69
+ # ── Tests ─────────────────────────────────────────────────────────────────────
70
+
71
+ class TestIFoodIntegration:
72
+ def test_initial_render(self, ifood_app):
73
+ mod, _ = ifood_app
74
+ tree = Render.execute(mod.create_view)
75
+ assert tree["type"] == "Column"
76
+
77
+ def test_restaurants_visible_in_html(self, ifood_app):
78
+ mod, _ = ifood_app
79
+ html = _render(mod.create_view)
80
+ assert "Domino" in html
81
+
82
+ def test_all_restaurants_initially(self, ifood_app):
83
+ mod, _ = ifood_app
84
+ html = _render(mod.create_view)
85
+ assert "Sushi Nagoya" in html
86
+ assert "Bob" in html
87
+ assert "Taco Loco" in html
88
+
89
+ def test_category_filter_sushi(self, ifood_app):
90
+ mod, _ = ifood_app
91
+ events.dispatch("select_category", "sushi")
92
+ html = _render(mod.create_view)
93
+ assert "Sushi Nagoya" in html
94
+ assert "Domino" not in html
95
+
96
+ def test_category_filter_pizza(self, ifood_app):
97
+ mod, _ = ifood_app
98
+ events.dispatch("select_category", "pizza")
99
+ html = _render(mod.create_view)
100
+ assert "Domino" in html
101
+ assert "Bob" not in html
102
+
103
+ def test_open_restaurant_sets_modal(self, ifood_app):
104
+ _, store = ifood_app
105
+ events.dispatch("open_restaurant", 1)
106
+ assert store.modal_visible is True
107
+ assert store.selected_restaurant_id == 1
108
+
109
+ def test_close_modal(self, ifood_app):
110
+ _, store = ifood_app
111
+ events.dispatch("open_restaurant", 1)
112
+ events.dispatch("close_modal")
113
+ assert store.modal_visible is False
114
+ assert store.selected_restaurant_id is None
115
+
116
+ def test_modal_content_visible_when_open(self, ifood_app):
117
+ mod, _ = ifood_app
118
+ events.dispatch("open_restaurant", 1)
119
+ html = _render(mod.create_view)
120
+ assert "Pepperoni" in html
121
+
122
+ def test_modal_hidden_initially(self, ifood_app):
123
+ mod, _ = ifood_app
124
+ html = _render(mod.create_view)
125
+ assert "display:none" in html
126
+
127
+ def test_add_to_cart(self, ifood_app):
128
+ _, store = ifood_app
129
+ events.dispatch("add_to_cart", 1)
130
+ assert len(store.cart) == 1
131
+
132
+ def test_cart_qty_increments(self, ifood_app):
133
+ _, store = ifood_app
134
+ events.dispatch("add_to_cart", 1)
135
+ events.dispatch("add_to_cart", 1)
136
+ assert len(store.cart) == 1
137
+ assert store.cart[0]["qty"] == 2
138
+
139
+ def test_add_different_items(self, ifood_app):
140
+ _, store = ifood_app
141
+ events.dispatch("add_to_cart", 1)
142
+ events.dispatch("add_to_cart", 4)
143
+ assert len(store.cart) == 2
144
+
145
+ def test_clear_cart(self, ifood_app):
146
+ _, store = ifood_app
147
+ events.dispatch("add_to_cart", 1)
148
+ events.dispatch("add_to_cart", 4)
149
+ events.dispatch("clear_cart")
150
+ assert store.cart == []
151
+
152
+ def test_locale_switch_to_en(self, ifood_app):
153
+ mod, _ = ifood_app
154
+ events.dispatch("switch_locale", "en")
155
+ html = _render(mod.create_view)
156
+ assert "Search" in html or "Nearby" in html
157
+
158
+ def test_locale_switch_updates_store(self, ifood_app):
159
+ _, store = ifood_app
160
+ events.dispatch("switch_locale", "en")
161
+ assert store.locale == "en"
162
+
163
+ def test_cart_bar_shown_when_items(self, ifood_app):
164
+ mod, _ = ifood_app
165
+ events.dispatch("add_to_cart", 1)
166
+ html = _render(mod.create_view)
167
+ assert "clear_cart" in html
168
+
169
+ def test_cart_bar_hidden_when_empty(self, ifood_app):
170
+ mod, _ = ifood_app
171
+ html = _render(mod.create_view)
172
+ assert "clear_cart" not in html
@@ -0,0 +1,133 @@
1
+ """
2
+ Test the p2m imagine CLI command
3
+ """
4
+
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+
9
+
10
+ def test_imagine_help():
11
+ """Test p2m imagine --help"""
12
+ print("\n🧪 Test 1: p2m imagine --help")
13
+
14
+ result = subprocess.run(
15
+ ["p2m", "imagine", "--help"],
16
+ capture_output=True,
17
+ text=True
18
+ )
19
+
20
+ assert result.returncode == 0, f"Command failed: {result.stderr}"
21
+ assert "P2M project" in result.stdout or "P2M code" in result.stdout or "natural language" in result.stdout
22
+ assert "--provider" in result.stdout
23
+ assert "--model" in result.stdout
24
+ assert "--api-key" in result.stdout
25
+
26
+ print("✅ Help command works correctly")
27
+ return True
28
+
29
+
30
+ def test_imagine_missing_description():
31
+ """Test p2m imagine without description"""
32
+ print("\n🧪 Test 2: p2m imagine (missing description)")
33
+
34
+ result = subprocess.run(
35
+ ["p2m", "imagine"],
36
+ capture_output=True,
37
+ text=True
38
+ )
39
+
40
+ # Should fail with missing argument
41
+ assert result.returncode != 0
42
+ print("✅ Correctly requires description argument")
43
+ return True
44
+
45
+
46
+ def test_imagine_version():
47
+ """Test p2m --version"""
48
+ print("\n🧪 Test 3: p2m --version")
49
+
50
+ result = subprocess.run(
51
+ ["p2m", "--version"],
52
+ capture_output=True,
53
+ text=True
54
+ )
55
+
56
+ # Version command may not be implemented, that's ok
57
+ if result.returncode == 0:
58
+ print(f"✅ Version: {result.stdout.strip()}")
59
+ else:
60
+ print(f"✅ Version command not implemented (optional)")
61
+ return True
62
+
63
+
64
+ def test_imagine_list_providers():
65
+ """Test listing available providers"""
66
+ print("\n🧪 Test 4: List Available Providers")
67
+
68
+ # Try to get help which lists providers
69
+ result = subprocess.run(
70
+ ["p2m", "imagine", "--help"],
71
+ capture_output=True,
72
+ text=True
73
+ )
74
+
75
+ assert "openai" in result.stdout.lower()
76
+ assert "anthropic" in result.stdout.lower()
77
+
78
+ print("✅ Providers listed in help:")
79
+ print(" - openai")
80
+ print(" - anthropic")
81
+ return True
82
+
83
+
84
+ def run_all_tests():
85
+ """Run all CLI tests"""
86
+ print("\n" + "="*60)
87
+ print("🧪 P2M Imagine CLI Test Suite")
88
+ print("="*60)
89
+
90
+ tests = [
91
+ test_imagine_help,
92
+ test_imagine_missing_description,
93
+ test_imagine_version,
94
+ test_imagine_list_providers,
95
+ ]
96
+
97
+ results = []
98
+
99
+ for test_func in tests:
100
+ try:
101
+ result = test_func()
102
+ results.append((test_func.__name__, result))
103
+ except Exception as e:
104
+ results.append((test_func.__name__, False))
105
+ print(f"❌ {test_func.__name__} failed: {e}")
106
+ import traceback
107
+ traceback.print_exc()
108
+
109
+ # Summary
110
+ print("\n" + "="*60)
111
+ print("📊 Test Summary")
112
+ print("="*60)
113
+
114
+ passed = sum(1 for _, success in results if success)
115
+ total = len(results)
116
+
117
+ for name, success in results:
118
+ status = "✅ PASS" if success else "❌ FAIL"
119
+ print(f"{status} - {name}")
120
+
121
+ print(f"\n📈 Results: {passed}/{total} tests passed")
122
+
123
+ if passed == total:
124
+ print("\n🎉 All tests passed!")
125
+ else:
126
+ print(f"\n⚠️ {total - passed} test(s) failed")
127
+
128
+ return passed == total
129
+
130
+
131
+ if __name__ == "__main__":
132
+ success = run_all_tests()
133
+ sys.exit(0 if success else 1)
@@ -0,0 +1,341 @@
1
+ """
2
+ Test suite for p2m imagine command
3
+ """
4
+
5
+ import sys
6
+ import tempfile
7
+ from pathlib import Path
8
+
9
+ # Add project to path
10
+ sys.path.insert(0, str(Path(__file__).parent.parent))
11
+
12
+ from p2m.imagine.legacy import CodeImaginer
13
+ from p2m.imagine import imagine_command
14
+
15
+
16
+ def test_config_validation_openai():
17
+ """Test OpenAI configuration validation"""
18
+ print("\n🧪 Test 1: OpenAI Config Validation")
19
+
20
+ imaginer = CodeImaginer(
21
+ provider="openai",
22
+ model="gpt-4o",
23
+ api_key="sk-test"
24
+ )
25
+
26
+ valid, msg = imaginer.validate_config()
27
+ assert valid, f"Should be valid: {msg}"
28
+ print("✅ OpenAI config validation passed")
29
+ return True
30
+
31
+
32
+ def test_config_validation_anthropic():
33
+ """Test Anthropic configuration validation"""
34
+ print("\n🧪 Test 2: Anthropic Config Validation")
35
+
36
+ imaginer = CodeImaginer(
37
+ provider="anthropic",
38
+ model="claude-3-opus-20240229",
39
+ api_key="sk-ant-test"
40
+ )
41
+
42
+ valid, msg = imaginer.validate_config()
43
+ assert valid, f"Should be valid: {msg}"
44
+ print("✅ Anthropic config validation passed")
45
+ return True
46
+
47
+
48
+ def test_config_validation_ollama():
49
+ """Test Ollama configuration validation"""
50
+ print("\n🧪 Test 3: Ollama Config Validation")
51
+
52
+ imaginer = CodeImaginer(
53
+ provider="ollama",
54
+ model="llama2",
55
+ base_url="http://localhost:11434"
56
+ )
57
+
58
+ valid, msg = imaginer.validate_config()
59
+ assert valid, f"Should be valid: {msg}"
60
+ assert imaginer.base_url == "http://localhost:11434"
61
+ print("✅ Ollama config validation passed")
62
+ return True
63
+
64
+
65
+ def test_config_validation_compatible():
66
+ """Test OpenAI-compatible configuration validation"""
67
+ print("\n🧪 Test 4: OpenAI-Compatible Config Validation")
68
+
69
+ imaginer = CodeImaginer(
70
+ provider="openai-compatible",
71
+ model="custom-model",
72
+ api_key="test-key",
73
+ base_url="https://api.example.com/v1"
74
+ )
75
+
76
+ valid, msg = imaginer.validate_config()
77
+ assert valid, f"Should be valid: {msg}"
78
+ print("✅ OpenAI-compatible config validation passed")
79
+ return True
80
+
81
+
82
+ def test_missing_api_key():
83
+ """Test missing API key validation"""
84
+ print("\n🧪 Test 5: Missing API Key Validation")
85
+
86
+ imaginer = CodeImaginer(
87
+ provider="openai",
88
+ model="gpt-4o",
89
+ api_key=None # No API key
90
+ )
91
+
92
+ # Clear environment variable if set
93
+ import os
94
+ old_key = os.environ.pop("OPENAI_API_KEY", None)
95
+
96
+ try:
97
+ valid, msg = imaginer.validate_config()
98
+ assert not valid, "Should be invalid without API key"
99
+ assert "OPENAI_API_KEY" in msg
100
+ print(f"✅ Correctly detected missing API key: {msg}")
101
+ return True
102
+ finally:
103
+ if old_key:
104
+ os.environ["OPENAI_API_KEY"] = old_key
105
+
106
+
107
+ def test_missing_base_url():
108
+ """Test missing base URL validation"""
109
+ print("\n🧪 Test 6: Missing Base URL Validation")
110
+
111
+ imaginer = CodeImaginer(
112
+ provider="openai-compatible",
113
+ model="custom-model",
114
+ api_key="test-key",
115
+ base_url=None # No base URL
116
+ )
117
+
118
+ valid, msg = imaginer.validate_config()
119
+ assert not valid, "Should be invalid without base_url"
120
+ assert "base-url" in msg.lower()
121
+ print(f"✅ Correctly detected missing base_url: {msg}")
122
+ return True
123
+
124
+
125
+ def test_code_validation_valid():
126
+ """Test code validation with valid code"""
127
+ print("\n🧪 Test 7: Code Validation - Valid Code")
128
+
129
+ valid_code = '''from p2m.core import Render
130
+ from p2m.ui import Container, Text
131
+
132
+ def create_view():
133
+ container = Container(class_="bg-white p-4")
134
+ text = Text("Hello World", class_="text-lg")
135
+ container.add(text)
136
+ return container.build()
137
+
138
+ def main():
139
+ Render.execute(create_view)
140
+
141
+ if __name__ == "__main__":
142
+ main()
143
+ '''
144
+
145
+ imaginer = CodeImaginer(
146
+ provider="openai",
147
+ model="gpt-4o",
148
+ api_key="test"
149
+ )
150
+
151
+ valid, msg = imaginer.validate_code(valid_code)
152
+ assert valid, f"Should be valid: {msg}"
153
+ print("✅ Valid code passed validation")
154
+ return True
155
+
156
+
157
+ def test_code_validation_syntax_error():
158
+ """Test code validation with syntax error"""
159
+ print("\n🧪 Test 8: Code Validation - Syntax Error")
160
+
161
+ invalid_code = '''from p2m.core import Render
162
+ from p2m.ui import Container, Text
163
+
164
+ def create_view(
165
+ # Missing closing parenthesis
166
+ container = Container()
167
+ '''
168
+
169
+ imaginer = CodeImaginer(
170
+ provider="openai",
171
+ model="gpt-4o",
172
+ api_key="test"
173
+ )
174
+
175
+ valid, msg = imaginer.validate_code(invalid_code)
176
+ assert not valid, "Should be invalid due to syntax error"
177
+ assert "Syntax error" in msg
178
+ print(f"✅ Correctly detected syntax error: {msg}")
179
+ return True
180
+
181
+
182
+ def test_code_validation_missing_imports():
183
+ """Test code validation with missing imports"""
184
+ print("\n🧪 Test 9: Code Validation - Missing Imports")
185
+
186
+ invalid_code = '''def create_view():
187
+ container = Container()
188
+ return container.build()
189
+ '''
190
+
191
+ imaginer = CodeImaginer(
192
+ provider="openai",
193
+ model="gpt-4o",
194
+ api_key="test"
195
+ )
196
+
197
+ valid, msg = imaginer.validate_code(invalid_code)
198
+ assert not valid, "Should be invalid without p2m imports"
199
+ assert "import from p2m" in msg.lower()
200
+ print(f"✅ Correctly detected missing imports: {msg}")
201
+ return True
202
+
203
+
204
+ def test_code_validation_missing_create_view():
205
+ """Test code validation with missing create_view"""
206
+ print("\n🧪 Test 10: Code Validation - Missing create_view")
207
+
208
+ invalid_code = '''from p2m.core import Render
209
+ from p2m.ui import Container, Text
210
+
211
+ def main():
212
+ print("Hello")
213
+ '''
214
+
215
+ imaginer = CodeImaginer(
216
+ provider="openai",
217
+ model="gpt-4o",
218
+ api_key="test"
219
+ )
220
+
221
+ valid, msg = imaginer.validate_code(invalid_code)
222
+ assert not valid, "Should be invalid without create_view"
223
+ assert "create_view" in msg
224
+ print(f"✅ Correctly detected missing create_view: {msg}")
225
+ return True
226
+
227
+
228
+ def test_save_code():
229
+ """Test saving generated code"""
230
+ print("\n🧪 Test 11: Save Generated Code")
231
+
232
+ code = '''from p2m.core import Render
233
+ from p2m.ui import Container, Text
234
+
235
+ def create_view():
236
+ container = Container(class_="bg-white p-4")
237
+ text = Text("Hello World", class_="text-lg")
238
+ container.add(text)
239
+ return container.build()
240
+ '''
241
+
242
+ imaginer = CodeImaginer(
243
+ provider="openai",
244
+ model="gpt-4o",
245
+ api_key="test"
246
+ )
247
+
248
+ with tempfile.TemporaryDirectory() as tmpdir:
249
+ output_file = Path(tmpdir) / "test_app.py"
250
+ success, filepath = imaginer.save_code(code, str(output_file))
251
+
252
+ assert success, f"Should save successfully: {filepath}"
253
+ assert Path(filepath).exists(), "File should exist"
254
+
255
+ # Verify content
256
+ saved_code = Path(filepath).read_text()
257
+ assert "create_view" in saved_code
258
+ assert "Container" in saved_code
259
+
260
+ print(f"✅ Code saved successfully to {filepath}")
261
+
262
+ return True
263
+
264
+
265
+ def test_provider_creation():
266
+ """Test LLM provider creation"""
267
+ print("\n🧪 Test 12: LLM Provider Creation")
268
+
269
+ imaginer = CodeImaginer(
270
+ provider="openai",
271
+ model="gpt-4o",
272
+ api_key="sk-test"
273
+ )
274
+
275
+ imaginer.validate_config()
276
+ success, msg = imaginer.create_provider()
277
+
278
+ assert success, f"Should create provider: {msg}"
279
+ assert imaginer.llm is not None
280
+ print(f"✅ Provider created successfully: {msg}")
281
+ return True
282
+
283
+
284
+ def run_all_tests():
285
+ """Run all tests"""
286
+ print("\n" + "="*60)
287
+ print("🧪 P2M Imagine Command Test Suite")
288
+ print("="*60)
289
+
290
+ tests = [
291
+ test_config_validation_openai,
292
+ test_config_validation_anthropic,
293
+ test_config_validation_ollama,
294
+ test_config_validation_compatible,
295
+ test_missing_api_key,
296
+ test_missing_base_url,
297
+ test_code_validation_valid,
298
+ test_code_validation_syntax_error,
299
+ test_code_validation_missing_imports,
300
+ test_code_validation_missing_create_view,
301
+ test_save_code,
302
+ test_provider_creation,
303
+ ]
304
+
305
+ results = []
306
+
307
+ for test_func in tests:
308
+ try:
309
+ result = test_func()
310
+ results.append((test_func.__name__, result))
311
+ except Exception as e:
312
+ results.append((test_func.__name__, False))
313
+ print(f"❌ {test_func.__name__} failed: {e}")
314
+ import traceback
315
+ traceback.print_exc()
316
+
317
+ # Summary
318
+ print("\n" + "="*60)
319
+ print("📊 Test Summary")
320
+ print("="*60)
321
+
322
+ passed = sum(1 for _, success in results if success)
323
+ total = len(results)
324
+
325
+ for name, success in results:
326
+ status = "✅ PASS" if success else "❌ FAIL"
327
+ print(f"{status} - {name}")
328
+
329
+ print(f"\n📈 Results: {passed}/{total} tests passed")
330
+
331
+ if passed == total:
332
+ print("\n🎉 All tests passed!")
333
+ else:
334
+ print(f"\n⚠️ {total - passed} test(s) failed")
335
+
336
+ return passed == total
337
+
338
+
339
+ if __name__ == "__main__":
340
+ success = run_all_tests()
341
+ sys.exit(0 if success else 1)