jac-client 0.1.0__py3-none-any.whl → 0.2.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 (141) hide show
  1. jac_client/docs/README.md +232 -172
  2. jac_client/docs/advanced-state.md +1012 -452
  3. jac_client/docs/asset-serving/intro.md +209 -0
  4. jac_client/docs/assets/pipe_line-v2.svg +32 -0
  5. jac_client/docs/assets/pipe_line.png +0 -0
  6. jac_client/docs/file-system/intro.md +90 -0
  7. jac_client/docs/guide-example/intro.md +117 -0
  8. jac_client/docs/guide-example/step-01-setup.md +260 -0
  9. jac_client/docs/guide-example/step-02-components.md +416 -0
  10. jac_client/docs/guide-example/step-03-styling.md +478 -0
  11. jac_client/docs/guide-example/step-04-todo-ui.md +477 -0
  12. jac_client/docs/guide-example/step-05-local-state.md +530 -0
  13. jac_client/docs/guide-example/step-06-events.md +750 -0
  14. jac_client/docs/guide-example/step-07-effects.md +469 -0
  15. jac_client/docs/guide-example/step-08-walkers.md +534 -0
  16. jac_client/docs/guide-example/step-09-authentication.md +586 -0
  17. jac_client/docs/guide-example/step-10-routing.md +540 -0
  18. jac_client/docs/guide-example/step-11-final.md +964 -0
  19. jac_client/docs/imports.md +538 -46
  20. jac_client/docs/lifecycle-hooks.md +517 -297
  21. jac_client/docs/routing.md +487 -357
  22. jac_client/docs/styling/intro.md +250 -0
  23. jac_client/docs/styling/js-styling.md +373 -0
  24. jac_client/docs/styling/material-ui.md +346 -0
  25. jac_client/docs/styling/pure-css.md +305 -0
  26. jac_client/docs/styling/sass.md +409 -0
  27. jac_client/docs/styling/styled-components.md +401 -0
  28. jac_client/docs/styling/tailwind.md +303 -0
  29. jac_client/examples/asset-serving/css-with-image/.babelrc +9 -0
  30. jac_client/examples/asset-serving/css-with-image/README.md +91 -0
  31. jac_client/examples/asset-serving/css-with-image/app.jac +67 -0
  32. jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
  33. jac_client/examples/asset-serving/css-with-image/package.json +28 -0
  34. jac_client/examples/asset-serving/css-with-image/styles.css +27 -0
  35. jac_client/examples/asset-serving/css-with-image/vite.config.js +29 -0
  36. jac_client/examples/asset-serving/image-asset/.babelrc +9 -0
  37. jac_client/examples/asset-serving/image-asset/README.md +119 -0
  38. jac_client/examples/asset-serving/image-asset/app.jac +43 -0
  39. jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
  40. jac_client/examples/asset-serving/image-asset/package.json +28 -0
  41. jac_client/examples/asset-serving/image-asset/styles.css +27 -0
  42. jac_client/examples/asset-serving/image-asset/vite.config.js +29 -0
  43. jac_client/examples/asset-serving/import-alias/.babelrc +9 -0
  44. jac_client/examples/asset-serving/import-alias/README.md +83 -0
  45. jac_client/examples/asset-serving/import-alias/app.jac +57 -0
  46. jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
  47. jac_client/examples/asset-serving/import-alias/package.json +28 -0
  48. jac_client/examples/asset-serving/import-alias/vite.config.js +29 -0
  49. jac_client/examples/basic/.babelrc +9 -0
  50. jac_client/examples/basic/README.md +16 -0
  51. jac_client/examples/basic/app.jac +16 -0
  52. jac_client/examples/basic/package.json +27 -0
  53. jac_client/examples/basic/vite.config.js +28 -0
  54. jac_client/examples/basic-auth/.babelrc +9 -0
  55. jac_client/examples/basic-auth/README.md +16 -0
  56. jac_client/examples/basic-auth/app.jac +308 -0
  57. jac_client/examples/basic-auth/package.json +27 -0
  58. jac_client/examples/basic-auth/vite.config.js +28 -0
  59. jac_client/examples/basic-auth-with-router/.babelrc +9 -0
  60. jac_client/examples/basic-auth-with-router/README.md +60 -0
  61. jac_client/examples/basic-auth-with-router/app.jac +464 -0
  62. jac_client/examples/basic-auth-with-router/package.json +28 -0
  63. jac_client/examples/basic-auth-with-router/vite.config.js +28 -0
  64. jac_client/examples/basic-full-stack/.babelrc +9 -0
  65. jac_client/examples/basic-full-stack/README.md +18 -0
  66. jac_client/examples/basic-full-stack/app.jac +320 -0
  67. jac_client/examples/basic-full-stack/package.json +28 -0
  68. jac_client/examples/basic-full-stack/vite.config.js +28 -0
  69. jac_client/examples/css-styling/js-styling/.babelrc +9 -0
  70. jac_client/examples/css-styling/js-styling/README.md +183 -0
  71. jac_client/examples/css-styling/js-styling/app.jac +63 -0
  72. jac_client/examples/css-styling/js-styling/package.json +28 -0
  73. jac_client/examples/css-styling/js-styling/styles.js +100 -0
  74. jac_client/examples/css-styling/js-styling/vite.config.js +28 -0
  75. jac_client/examples/css-styling/material-ui/.babelrc +9 -0
  76. jac_client/examples/css-styling/material-ui/README.md +16 -0
  77. jac_client/examples/css-styling/material-ui/app.jac +82 -0
  78. jac_client/examples/css-styling/material-ui/package.json +32 -0
  79. jac_client/examples/css-styling/material-ui/vite.config.js +28 -0
  80. jac_client/examples/css-styling/pure-css/.babelrc +9 -0
  81. jac_client/examples/css-styling/pure-css/README.md +16 -0
  82. jac_client/examples/css-styling/pure-css/app.jac +63 -0
  83. jac_client/examples/css-styling/pure-css/package.json +28 -0
  84. jac_client/examples/css-styling/pure-css/styles.css +112 -0
  85. jac_client/examples/css-styling/pure-css/vite.config.js +28 -0
  86. jac_client/examples/css-styling/sass-example/.babelrc +9 -0
  87. jac_client/examples/css-styling/sass-example/README.md +16 -0
  88. jac_client/examples/css-styling/sass-example/app.jac +63 -0
  89. jac_client/examples/css-styling/sass-example/package.json +29 -0
  90. jac_client/examples/css-styling/sass-example/styles.scss +158 -0
  91. jac_client/examples/css-styling/sass-example/vite.config.js +28 -0
  92. jac_client/examples/css-styling/styled-components/.babelrc +9 -0
  93. jac_client/examples/css-styling/styled-components/README.md +16 -0
  94. jac_client/examples/css-styling/styled-components/app.jac +66 -0
  95. jac_client/examples/css-styling/styled-components/package.json +29 -0
  96. jac_client/examples/css-styling/styled-components/styled.js +91 -0
  97. jac_client/examples/css-styling/styled-components/vite.config.js +28 -0
  98. jac_client/examples/css-styling/tailwind-example/.babelrc +9 -0
  99. jac_client/examples/css-styling/tailwind-example/README.md +16 -0
  100. jac_client/examples/css-styling/tailwind-example/app.jac +64 -0
  101. jac_client/examples/css-styling/tailwind-example/global.css +1 -0
  102. jac_client/examples/css-styling/tailwind-example/package.json +30 -0
  103. jac_client/examples/css-styling/tailwind-example/vite.config.js +30 -0
  104. jac_client/examples/full-stack-with-auth/.babelrc +9 -0
  105. jac_client/examples/full-stack-with-auth/README.md +16 -0
  106. jac_client/examples/full-stack-with-auth/app.jac +735 -0
  107. jac_client/examples/full-stack-with-auth/package.json +28 -0
  108. jac_client/examples/full-stack-with-auth/vite.config.js +30 -0
  109. jac_client/examples/with-router/.babelrc +9 -0
  110. jac_client/examples/with-router/README.md +17 -0
  111. jac_client/examples/with-router/app.jac +323 -0
  112. jac_client/examples/with-router/package.json +28 -0
  113. jac_client/examples/with-router/vite.config.js +28 -0
  114. jac_client/plugin/cli.py +95 -179
  115. jac_client/plugin/client.py +111 -2
  116. jac_client/plugin/client_runtime.jac +183 -890
  117. jac_client/plugin/vite_client_bundle.py +185 -205
  118. jac_client/tests/__init__.py +0 -1
  119. jac_client/tests/fixtures/{client_app.jac → basic-app/app.jac} +1 -1
  120. jac_client/tests/fixtures/cl_file/app.cl.jac +38 -0
  121. jac_client/tests/fixtures/cl_file/app.jac +15 -0
  122. jac_client/tests/fixtures/{client_app_with_antd.jac → client_app_with_antd/app.jac} +7 -0
  123. jac_client/tests/fixtures/{js_import.jac → js_import/app.jac} +2 -2
  124. jac_client/tests/fixtures/{relative_import.jac → relative_import/app.jac} +1 -1
  125. jac_client/tests/fixtures/{button.jac → relative_import/button.jac} +2 -2
  126. jac_client/tests/fixtures/spawn_test/app.jac +133 -0
  127. jac_client/tests/fixtures/{test_fragments_spread.jac → test_fragments_spread/app.jac} +11 -2
  128. jac_client/tests/test_asset_examples.py +339 -0
  129. jac_client/tests/test_cl.py +345 -151
  130. jac_client/tests/test_create_jac_app.py +41 -45
  131. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/METADATA +72 -16
  132. jac_client-0.2.1.dist-info/RECORD +140 -0
  133. jac_client/examples/little-x/package-lock.json +0 -2840
  134. jac_client/examples/todo-app/README.md +0 -82
  135. jac_client/examples/todo-app/app.jac +0 -683
  136. jac_client/examples/todo-app/package-lock.json +0 -999
  137. jac_client/examples/todo-app/package.json +0 -22
  138. jac_client-0.1.0.dist-info/RECORD +0 -33
  139. /jac_client/tests/fixtures/{utils.js → js_import/utils.js} +0 -0
  140. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/WHEEL +0 -0
  141. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/entry_points.txt +0 -0
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from pathlib import Path
6
+ import json
6
7
  import tempfile
7
8
  import subprocess
8
9
 
@@ -21,54 +22,125 @@ class ViteClientBundleBuilderTests(TestCase):
21
22
  def tearDown(self) -> None:
22
23
  Jac.reset_machine()
23
24
  return super().tearDown()
24
-
25
- def _create_test_project_with_vite(self, temp_path: Path, include_antd: bool = False) -> tuple[Path, Path]:
25
+
26
+ def _create_test_project_with_vite(
27
+ self, temp_path: Path, include_antd: bool = False
28
+ ) -> tuple[Path, Path]:
26
29
  """Create a minimal test project with Vite installed.
27
-
30
+
28
31
  Args:
29
32
  temp_path: Path to the temporary directory
30
33
  include_antd: If True, includes antd in dependencies
31
34
  """
32
35
  # Create package.json with base dependencies
33
36
  dependencies = {
34
- "react": "^18.0.0",
35
- "react-dom": "^18.0.0"
37
+ "react": "^19.2.0",
38
+ "react-dom": "^19.2.0",
39
+ "react-router-dom": "^7.3.0",
36
40
  }
37
-
41
+
38
42
  # Add antd if requested
39
43
  if include_antd:
40
44
  dependencies["antd"] = "^5.0.0"
41
-
42
- # Format dependencies for JSON
43
- deps_str = ",\n".join(f' "{k}": "{v}"' for k, v in dependencies.items())
44
-
45
- package_json = temp_path / "package.json"
46
- package_json.write_text(f"""{{
45
+
46
+ # Create package.json structure
47
+ package_data = {
47
48
  "name": "test-client",
48
49
  "version": "0.0.1",
49
- "dependencies": {{
50
- {deps_str}
51
- }},
52
- "devDependencies": {{
53
- "vite": "^5.0.0"
54
- }}
55
- }}""", encoding="utf-8")
56
-
50
+ "type": "module",
51
+ "scripts": {
52
+ "build": "npm run compile && vite build",
53
+ "dev": "vite dev",
54
+ "preview": "vite preview",
55
+ "compile": 'babel src --out-dir build --extensions ".jsx,.js" --out-file-extension .js',
56
+ },
57
+ "dependencies": dependencies,
58
+ "devDependencies": {
59
+ "vite": "^6.4.1",
60
+ "@babel/cli": "^7.28.3",
61
+ "@babel/core": "^7.28.5",
62
+ "@babel/preset-env": "^7.28.5",
63
+ "@babel/preset-react": "^7.28.5",
64
+ },
65
+ }
66
+
67
+ package_json = temp_path / "package.json"
68
+ with package_json.open("w", encoding="utf-8") as f:
69
+ json.dump(package_data, f, indent=2)
70
+
71
+ # Create .babelrc file
72
+ babelrc = temp_path / ".babelrc"
73
+ babelrc.write_text(
74
+ """{
75
+ "presets": [[
76
+ "@babel/preset-env",
77
+ {
78
+ "modules": false
79
+ }
80
+ ], "@babel/preset-react"]
81
+ }
82
+ """,
83
+ encoding="utf-8",
84
+ )
85
+
86
+ # Create vite.config.js file
87
+ vite_config = temp_path / "vite.config.js"
88
+ vite_config.write_text(
89
+ """import { defineConfig } from "vite";
90
+ import path from "path";
91
+ import { fileURLToPath } from "url";
92
+
93
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
94
+
95
+ export default defineConfig({
96
+ root: ".",
97
+ build: {
98
+ rollupOptions: {
99
+ input: "build/main.js",
100
+ output: {
101
+ entryFileNames: "client.[hash].js",
102
+ assetFileNames: "[name].[ext]",
103
+ },
104
+ },
105
+ outDir: "dist",
106
+ emptyOutDir: true,
107
+ minify: false,
108
+ },
109
+ publicDir: false,
110
+ resolve: {
111
+ alias: {
112
+ "@jac-client/utils": path.resolve(__dirname, "src/client_runtime.js"),
113
+ },
114
+ },
115
+ });
116
+ """,
117
+ encoding="utf-8",
118
+ )
119
+
57
120
  # Install dependencies
58
- subprocess.run(
121
+ result = subprocess.run(
59
122
  ["npm", "install"],
60
123
  cwd=temp_path,
61
- check=True,
124
+ check=False,
62
125
  capture_output=True,
126
+ text=True,
63
127
  )
64
-
128
+ if result.returncode != 0:
129
+ error_msg = f"npm install failed with exit code {result.returncode}\n"
130
+ error_msg += f"stdout: {result.stdout}\n"
131
+ error_msg += f"stderr: {result.stderr}\n"
132
+ raise RuntimeError(error_msg)
133
+
65
134
  # Create output directory
66
- output_dir = temp_path / "static" / "client" / "js"
135
+ output_dir = temp_path / "dist"
67
136
  output_dir.mkdir(parents=True, exist_ok=True)
68
-
69
- temp_dir = temp_path / "temp"
70
- temp_dir.mkdir(parents=True, exist_ok=True)
71
-
137
+
138
+ src_dir = temp_path / "src"
139
+ src_dir.mkdir(parents=True, exist_ok=True)
140
+
141
+ build_dir = temp_path / "build"
142
+ build_dir.mkdir(parents=True, exist_ok=True)
143
+
72
144
  return package_json, output_dir
73
145
 
74
146
  def test_build_bundle_with_vite(self) -> None:
@@ -76,44 +148,41 @@ class ViteClientBundleBuilderTests(TestCase):
76
148
  # Create a temporary directory for our test project
77
149
  with tempfile.TemporaryDirectory() as temp_dir:
78
150
  temp_path = Path(temp_dir)
79
-
80
- # Create project with Vite installed
151
+
81
152
  package_json, output_dir = self._create_test_project_with_vite(temp_path)
82
-
153
+ runtime_path = (
154
+ Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
155
+ )
83
156
  # Initialize the Vite builder
84
157
  builder = ViteClientBundleBuilder(
158
+ runtime_path=runtime_path,
85
159
  vite_package_json=package_json,
86
160
  vite_output_dir=output_dir,
87
161
  vite_minify=False, # Disable minification for easier inspection
88
162
  )
89
-
90
163
  # Import the test module
91
- fixtures_dir = Path(__file__).parent / "fixtures"
92
- (module,) = Jac.jac_import("client_app", str(fixtures_dir))
93
-
164
+ fixtures_dir = Path(__file__).parent / "fixtures" / "basic-app"
165
+ (module,) = Jac.jac_import("app", str(fixtures_dir))
94
166
  # Build the bundle
95
167
  bundle = builder.build(module, force=True)
96
-
97
- # Verify bundle structure
168
+
98
169
  self.assertIsNotNone(bundle)
99
- self.assertEqual(bundle.module_name, "client_app")
100
- self.assertIn("client_page", bundle.client_functions)
170
+ self.assertEqual(bundle.module_name, "app")
171
+ self.assertIn("app", bundle.client_functions)
101
172
  self.assertIn("ButtonProps", bundle.client_functions)
102
173
  self.assertIn("API_LABEL", bundle.client_globals)
103
174
  self.assertGreater(len(bundle.hash), 10)
104
-
175
+
105
176
  # Verify bundle code contains expected content
106
- self.assertIn("function client_page()", bundle.code)
107
- self.assertIn('const API_LABEL = "Runtime Test";', bundle.code)
108
-
109
- # Verify the Jac initialization is present
110
- self.assertIn("__jacRegisterClientModule", bundle.code)
111
- self.assertIn("globalThis.start_app", bundle.code)
112
-
177
+ self.assertIn("function app()", bundle.code)
178
+ self.assertIn('API_LABEL = "Runtime Test";', bundle.code)
179
+
113
180
  # Verify bundle was written to output directory
114
181
  bundle_files = list(output_dir.glob("client.*.js"))
115
- self.assertGreater(len(bundle_files), 0, "Expected at least one bundle file")
116
-
182
+ self.assertGreater(
183
+ len(bundle_files), 0, "Expected at least one bundle file"
184
+ )
185
+
117
186
  # Verify cached bundle is identical
118
187
  cached = builder.build(module, force=False)
119
188
  self.assertEqual(bundle.hash, cached.hash)
@@ -121,95 +190,79 @@ class ViteClientBundleBuilderTests(TestCase):
121
190
 
122
191
  def test_vite_bundle_without_package_json(self) -> None:
123
192
  """Test that missing package.json raises appropriate error."""
124
- fixtures_dir = Path(__file__).parent / "fixtures"
125
- (module,) = Jac.jac_import("client_app", str(fixtures_dir))
126
-
193
+ fixtures_dir = Path(__file__).parent / "fixtures" / "basic-app"
194
+ (module,) = Jac.jac_import("app", str(fixtures_dir))
195
+
196
+ runtime_path = Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
197
+
127
198
  # Create builder without package.json
128
199
  builder = ViteClientBundleBuilder(
200
+ runtime_path=runtime_path,
129
201
  vite_package_json=Path("/nonexistent/package.json"),
130
202
  vite_output_dir=Path("/tmp/output"),
131
203
  )
132
-
204
+
133
205
  # Building should raise an error
134
206
  from jaclang.runtimelib.client_bundle import ClientBundleError
207
+
135
208
  with self.assertRaises(ClientBundleError) as cm:
136
209
  builder.build(module, force=True)
137
-
138
- self.assertIn("Vite package.json not found", str(cm.exception))
139
210
 
140
- def test_global_exposure_in_bundle(self) -> None:
141
- """Test that client functions are properly exposed globally for Vite IIFE."""
142
- with tempfile.TemporaryDirectory() as temp_dir:
143
- temp_path = Path(temp_dir)
144
-
145
- # Create project with Vite installed
146
- package_json, output_dir = self._create_test_project_with_vite(temp_path)
147
-
148
- # Initialize the Vite builder
149
- builder = ViteClientBundleBuilder(
150
- vite_package_json=package_json,
151
- vite_output_dir=output_dir,
152
- )
153
-
154
- # Import the test module
155
- fixtures_dir = Path(__file__).parent / "fixtures"
156
- (module,) = Jac.jac_import("client_app", str(fixtures_dir))
157
-
158
- # Build the bundle
159
- bundle = builder.build(module, force=True)
160
-
161
- # Verify global exposure code is present
162
- # Note: Variable names may be minified, so we check for the concept rather than exact strings
163
- self.assertIn("__jacEnsureHydration", bundle.code)
164
- self.assertIn("globalThis.start_app()", bundle.code)
165
-
166
- # Cleanup
167
- builder.cleanup_temp_dir()
211
+ self.assertIn("Vite package.json not found", str(cm.exception))
168
212
 
169
213
  def test_build_bundle_with_antd(self) -> None:
170
214
  """Test that Vite bundling works with Ant Design components."""
171
215
  with tempfile.TemporaryDirectory() as temp_dir:
216
+
172
217
  temp_path = Path(temp_dir)
173
-
218
+
174
219
  # Create project with Vite and Ant Design installed
175
- package_json, output_dir = self._create_test_project_with_vite(temp_path, include_antd=True)
176
-
220
+ package_json, output_dir = self._create_test_project_with_vite(
221
+ temp_path, include_antd=True
222
+ )
223
+ runtime_path = (
224
+ Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
225
+ )
226
+
177
227
  # Initialize the Vite builder
178
228
  builder = ViteClientBundleBuilder(
229
+ runtime_path=runtime_path,
179
230
  vite_package_json=package_json,
180
231
  vite_output_dir=output_dir,
181
232
  vite_minify=False,
182
233
  )
183
-
234
+
184
235
  # Import the test module with Ant Design
185
- fixtures_dir = Path(__file__).parent / "fixtures"
186
- (module,) = Jac.jac_import("client_app_with_antd", str(fixtures_dir))
187
-
236
+ fixtures_dir = Path(__file__).parent / "fixtures" / "client_app_with_antd"
237
+ (module,) = Jac.jac_import("app", str(fixtures_dir))
238
+
188
239
  # Build the bundle
189
240
  bundle = builder.build(module, force=True)
190
-
241
+
191
242
  # Verify bundle structure
192
243
  self.assertIsNotNone(bundle)
193
- self.assertEqual(bundle.module_name, "client_app_with_antd")
244
+ self.assertEqual(bundle.module_name, "app")
194
245
  self.assertIn("ButtonTest", bundle.client_functions)
195
246
  self.assertIn("CardTest", bundle.client_functions)
196
247
  self.assertIn("APP_NAME", bundle.client_globals)
197
-
248
+
198
249
  # Verify bundle code contains expected content
199
250
  self.assertIn("function ButtonTest()", bundle.code)
200
251
  self.assertIn("function CardTest()", bundle.code)
201
- self.assertIn('const APP_NAME = "Ant Design Test";', bundle.code)
202
-
252
+ self.assertIn('APP_NAME = "Ant Design Test";', bundle.code)
253
+
203
254
  # verify antd components are present
204
255
  self.assertIn("ButtonGroup", bundle.code)
205
256
 
206
257
  # Verify the Ant Design fixture content is present
207
258
  self.assertIn("Testing Ant Design integration", bundle.code)
208
-
259
+
209
260
  # Verify bundle was written to output directory
210
261
  bundle_files = list(output_dir.glob("client.*.js"))
211
- self.assertGreater(len(bundle_files), 0, "Expected at least one bundle file")
212
-
262
+ self.assertGreater(
263
+ len(bundle_files), 0, "Expected at least one bundle file"
264
+ )
265
+
213
266
  # Cleanup
214
267
  builder.cleanup_temp_dir()
215
268
 
@@ -217,45 +270,50 @@ class ViteClientBundleBuilderTests(TestCase):
217
270
  """Test that relative imports work correctly in Vite bundling."""
218
271
  with tempfile.TemporaryDirectory() as temp_dir:
219
272
  temp_path = Path(temp_dir)
220
-
273
+
221
274
  # Create project with Vite installed
222
- package_json, output_dir = self._create_test_project_with_vite(temp_path, include_antd=True)
223
-
275
+ package_json, output_dir = self._create_test_project_with_vite(
276
+ temp_path, include_antd=True
277
+ )
278
+ runtime_path = (
279
+ Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
280
+ )
281
+
224
282
  # Initialize the Vite builder
225
283
  builder = ViteClientBundleBuilder(
284
+ runtime_path=runtime_path,
226
285
  vite_package_json=package_json,
227
286
  vite_output_dir=output_dir,
228
287
  vite_minify=False,
229
288
  )
230
-
289
+
231
290
  # Import the test module with relative import
232
- fixtures_dir = Path(__file__).parent / "fixtures"
233
- (module,) = Jac.jac_import("relative_import", str(fixtures_dir))
234
-
291
+ fixtures_dir = Path(__file__).parent / "fixtures" / "relative_import"
292
+ (module,) = Jac.jac_import("app", str(fixtures_dir))
293
+
235
294
  # Build the bundle
236
295
  bundle = builder.build(module, force=True)
237
-
296
+
238
297
  # Verify bundle structure
239
298
  self.assertIsNotNone(bundle)
240
- self.assertEqual(bundle.module_name, "relative_import")
299
+ self.assertEqual(bundle.module_name, "app")
241
300
  self.assertIn("RelativeImport", bundle.client_functions)
242
- self.assertIn("main", bundle.client_functions)
301
+ self.assertIn("app", bundle.client_functions)
243
302
  self.assertIn("CustomButton", bundle.code)
244
-
303
+
245
304
  # Verify bundle code contains expected content
246
305
  self.assertIn("function RelativeImport()", bundle.code)
247
- self.assertIn("function main()", bundle.code)
248
-
306
+ self.assertIn("function app()", bundle.code)
307
+
249
308
  # Verify that the relative import (Button from .button) is properly resolved
250
309
  self.assertIn("ButtonGroup", bundle.code)
251
-
252
- # Verify the Jac initialization is present
253
- self.assertIn("__jacRegisterClientModule", bundle.code)
254
-
310
+
255
311
  # Verify bundle was written to output directory
256
312
  bundle_files = list(output_dir.glob("client.*.js"))
257
- self.assertGreater(len(bundle_files), 0, "Expected at least one bundle file")
258
-
313
+ self.assertGreater(
314
+ len(bundle_files), 0, "Expected at least one bundle file"
315
+ )
316
+
259
317
  # Cleanup
260
318
  builder.cleanup_temp_dir()
261
319
 
@@ -265,96 +323,232 @@ class ViteClientBundleBuilderTests(TestCase):
265
323
  temp_path = Path(temp_dir)
266
324
  # Create project with Vite installed
267
325
  package_json, output_dir = self._create_test_project_with_vite(temp_path)
268
-
326
+ runtime_path = (
327
+ Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
328
+ )
269
329
  # Initialize the Vite builder
270
330
  builder = ViteClientBundleBuilder(
331
+ runtime_path=runtime_path,
271
332
  vite_package_json=package_json,
272
333
  vite_output_dir=output_dir,
273
334
  vite_minify=False,
274
335
  )
275
-
336
+
276
337
  # Import the test module with JavaScript import
277
- fixtures_dir = Path(__file__).parent / "fixtures"
278
- (module,) = Jac.jac_import("js_import", str(fixtures_dir))
279
-
338
+ fixtures_dir = Path(__file__).parent / "fixtures" / "js_import"
339
+ (module,) = Jac.jac_import("app", str(fixtures_dir))
340
+
280
341
  # Build the bundle
281
342
  bundle = builder.build(module, force=True)
282
-
343
+
283
344
  # Verify bundle structure
284
345
  self.assertIsNotNone(bundle)
285
- self.assertEqual(bundle.module_name, "js_import")
346
+ self.assertEqual(bundle.module_name, "app")
286
347
  self.assertIn("JsImportTest", bundle.client_functions)
287
- self.assertIn("Main", bundle.client_functions)
348
+ self.assertIn("app", bundle.client_functions)
288
349
  self.assertIn("JS_IMPORT_LABEL", bundle.client_globals)
289
-
350
+
290
351
  # Verify bundle code contains expected content
291
352
  self.assertIn("function JsImportTest()", bundle.code)
292
- self.assertIn("function Main()", bundle.code)
293
- self.assertIn('const JS_IMPORT_LABEL = "JavaScript Import Test";', bundle.code)
294
-
353
+ self.assertIn("function app()", bundle.code)
354
+ self.assertIn('JS_IMPORT_LABEL = "JavaScript Import Test";', bundle.code)
355
+
295
356
  # Verify JavaScript imports are present in the bundle
296
357
  # The JavaScript functions should be available in the bundle
297
358
  self.assertIn("formatMessage", bundle.code)
298
359
  self.assertIn("calculateSum", bundle.code)
299
360
  self.assertIn("JS_CONSTANT", bundle.code)
300
361
  self.assertIn("MessageFormatter", bundle.code)
301
-
362
+
302
363
  # Verify the JavaScript utility code is included
303
364
  self.assertIn("Hello,", bundle.code) # From formatMessage function
304
365
  self.assertIn("Imported from JavaScript", bundle.code) # From JS_CONSTANT
305
-
306
- # Verify the Jac initialization is present
307
- self.assertIn("__jacRegisterClientModule", bundle.code)
308
-
366
+
309
367
  # Verify bundle was written to output directory
310
368
  bundle_files = list(output_dir.glob("client.*.js"))
311
- self.assertGreater(len(bundle_files), 0, "Expected at least one bundle file")
312
-
369
+ self.assertGreater(
370
+ len(bundle_files), 0, "Expected at least one bundle file"
371
+ )
372
+
313
373
  # Cleanup
314
374
  builder.cleanup_temp_dir()
315
-
375
+
316
376
  def test_jsx_fragments_and_spread_props(self) -> None:
317
377
  """Test that JSX fragments and spread props work correctly."""
318
378
  with tempfile.TemporaryDirectory() as temp_dir:
319
379
  temp_path = Path(temp_dir)
320
-
380
+
321
381
  # Create project with Vite installed
322
382
  package_json, output_dir = self._create_test_project_with_vite(temp_path)
323
-
383
+ runtime_path = (
384
+ Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
385
+ )
386
+
324
387
  # Initialize the Vite builder
325
388
  builder = ViteClientBundleBuilder(
389
+ runtime_path=runtime_path,
326
390
  vite_package_json=package_json,
327
391
  vite_output_dir=output_dir,
328
392
  vite_minify=False,
329
393
  )
330
-
394
+
331
395
  # Import the test module with fragments and spread props
332
- fixtures_dir = Path(__file__).parent / "fixtures"
333
- (module,) = Jac.jac_import("test_fragments_spread", str(fixtures_dir))
334
-
396
+ fixtures_dir = Path(__file__).parent / "fixtures" / "test_fragments_spread"
397
+ (module,) = Jac.jac_import("app", str(fixtures_dir))
398
+
335
399
  # Build the bundle
336
400
  bundle = builder.build(module, force=True)
337
-
401
+
338
402
  # Verify bundle structure
339
403
  self.assertIsNotNone(bundle)
340
- self.assertEqual(bundle.module_name, "test_fragments_spread")
404
+ self.assertEqual(bundle.module_name, "app")
341
405
  self.assertIn("FragmentTest", bundle.client_functions)
342
406
  self.assertIn("SpreadPropsTest", bundle.client_functions)
343
407
  self.assertIn("MixedTest", bundle.client_functions)
344
408
  self.assertIn("NestedFragments", bundle.client_functions)
345
-
409
+
346
410
  # Verify spread props handling (Object.assign is used by compiler)
347
411
  self.assertIn("Object.assign", bundle.code)
348
-
412
+
349
413
  # Verify fragment test function exists
350
414
  self.assertIn("function FragmentTest()", bundle.code)
351
-
415
+
352
416
  # Verify spread props test function exists
353
417
  self.assertIn("function SpreadPropsTest()", bundle.code)
354
-
418
+
355
419
  # Verify bundle was written to output directory
356
420
  bundle_files = list(output_dir.glob("client.*.js"))
357
- self.assertGreater(len(bundle_files), 0, "Expected at least one bundle file")
358
-
421
+ self.assertGreater(
422
+ len(bundle_files), 0, "Expected at least one bundle file"
423
+ )
424
+
425
+ # Cleanup
426
+ builder.cleanup_temp_dir()
427
+
428
+ def test_spawn_operator(self) -> None:
429
+ """Test that spawn operator generates correct __jacSpawn calls for both orderings and node types (root and UUID)."""
430
+ with tempfile.TemporaryDirectory() as temp_dir:
431
+ temp_path = Path(temp_dir)
432
+
433
+ # Create project with Vite installed
434
+ package_json, output_dir = self._create_test_project_with_vite(temp_path)
435
+ runtime_path = (
436
+ Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
437
+ )
438
+
439
+ # Initialize the Vite builder
440
+ builder = ViteClientBundleBuilder(
441
+ runtime_path=runtime_path,
442
+ vite_package_json=package_json,
443
+ vite_output_dir=output_dir,
444
+ vite_minify=False,
445
+ )
446
+
447
+ # Import the test module with both spawn operator orderings
448
+ fixtures_dir = Path(__file__).parent / "fixtures" / "spawn_test"
449
+ (module,) = Jac.jac_import("app", str(fixtures_dir))
450
+
451
+ # Build the bundle
452
+ bundle = builder.build(module, force=True)
453
+
454
+ # Verify bundle structure
455
+ self.assertIsNotNone(bundle)
456
+ self.assertEqual(bundle.module_name, "app")
457
+ self.assertIn("app", bundle.client_functions)
458
+
459
+ # Verify complete __jacSpawn calls for root spawn scenarios
460
+ # Standard order: root spawn test_walker()
461
+ self.assertIn('__jacSpawn("test_walker", "", {})', bundle.code)
462
+
463
+ # Standard order: root spawn parameterized_walker(value=42)
464
+ self.assertIn('__jacSpawn("parameterized_walker", "", {', bundle.code)
465
+ self.assertIn('"value": 42', bundle.code)
466
+
467
+ # Reverse order: test_walker(message="Reverse spawn!") spawn root
468
+ # Should generate: __jacSpawn("test_walker", "", {message: "Reverse spawn!"})
469
+ self.assertRegex(
470
+ bundle.code,
471
+ r'__jacSpawn\("test_walker",\s*"",\s*\{[^}]*"message":\s*"Reverse spawn!"[^}]*\}\)',
472
+ )
473
+
474
+ # Verify UUID spawn scenarios with complete calls
475
+ # Standard UUID spawn: node_id spawn test_walker()
476
+ # Should generate: __jacSpawn("test_walker", node_id, {})
477
+ self.assertIn('__jacSpawn("test_walker", node_id, {})', bundle.code)
478
+ self.assertIn('"550e8400-e29b-41d4-a716-446655440000"', bundle.code)
479
+
480
+ # Reverse UUID spawn: parameterized_walker(value=100) spawn another_node_id
481
+ # Should generate: __jacSpawn("parameterized_walker", another_node_id, {value: 100})
482
+ self.assertRegex(
483
+ bundle.code,
484
+ r'__jacSpawn\("parameterized_walker",\s*another_node_id,\s*\{[^}]*"value":\s*100[^}]*\}\)',
485
+ )
486
+ self.assertIn('"6ba7b810-9dad-11d1-80b4-00c04fd430c8"', bundle.code)
487
+
488
+ # Verify positional argument mapping for walkers
489
+ self.assertRegex(
490
+ bundle.code,
491
+ r'__jacSpawn\("positional_walker",\s*node_id,\s*\{[^}]*"label":\s*"Node positional"[^}]*"count":\s*2',
492
+ )
493
+ # Verify spread (**kwargs) handling when walker is on left-hand side
494
+ self.assertRegex(
495
+ bundle.code,
496
+ r'__jacSpawn\("positional_walker",\s*"",\s*_objectSpread\(\{\s*"label":\s*"Spread order"[^}]*"count":\s*5\s*\},\s*extra_fields\)',
497
+ )
498
+
499
+ # Verify we have at least 7 __jacSpawn calls (previous cases + new positional/spread)
500
+ self.assertTrue(
501
+ bundle.code.count("__jacSpawn") >= 7,
502
+ "Expected at least 7 __jacSpawn calls in bundle",
503
+ )
504
+
505
+ # Verify bundle was written to output directory
506
+ bundle_files = list(output_dir.glob("client.*.js"))
507
+ self.assertGreater(
508
+ len(bundle_files), 0, "Expected at least one bundle file"
509
+ )
510
+
511
+ # Cleanup
512
+ builder.cleanup_temp_dir()
513
+
514
+ def test_serve_cl_file(self) -> None:
515
+ """Test that serving a .cl file works correctly."""
516
+ with tempfile.TemporaryDirectory() as temp_dir:
517
+ temp_path = Path(temp_dir)
518
+
519
+ # Create project with Vite installed
520
+ package_json, output_dir = self._create_test_project_with_vite(temp_path)
521
+ runtime_path = (
522
+ Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
523
+ )
524
+
525
+ # Initialize the Vite builder
526
+ builder = ViteClientBundleBuilder(
527
+ runtime_path=runtime_path,
528
+ vite_package_json=package_json,
529
+ vite_output_dir=output_dir,
530
+ vite_minify=False,
531
+ )
532
+
533
+ # Import the test module with both spawn operator orderings
534
+ fixtures_dir = Path(__file__).parent / "fixtures" / "cl_file"
535
+ (module,) = Jac.jac_import("app", str(fixtures_dir))
536
+
537
+ # Build the bundle
538
+ bundle = builder.build(module, force=True)
539
+ # Verify bundle structure
540
+ self.assertIsNotNone(bundle)
541
+ self.assertEqual(bundle.module_name, "app")
542
+ self.assertIn("app", bundle.client_functions)
543
+
544
+ self.assertIn("function app()", bundle.code)
545
+ self.assertIn(
546
+ '__jacJsx("div", {}, [__jacJsx("h2", {}, ["My Todos"])', bundle.code
547
+ )
548
+ self.assertIn("root.render(/* @__PURE__ */ React.c", bundle.code)
549
+ self.assertIn(
550
+ "ar _useState = reactExports.useState([]), _useStat", bundle.code
551
+ )
552
+ self.assertIn('turn __jacSpawn("create_todo", ', bundle.code)
359
553
  # Cleanup
360
554
  builder.cleanup_temp_dir()