jac-client 0.2.0__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.
- jac_client/docs/README.md +659 -0
- jac_client/docs/advanced-state.md +1266 -0
- jac_client/docs/assets/pipe_line.png +0 -0
- jac_client/docs/guide-example/intro.md +117 -0
- jac_client/docs/guide-example/step-01-setup.md +260 -0
- jac_client/docs/guide-example/step-02-components.md +416 -0
- jac_client/docs/guide-example/step-03-styling.md +478 -0
- jac_client/docs/guide-example/step-04-todo-ui.md +477 -0
- jac_client/docs/guide-example/step-05-local-state.md +530 -0
- jac_client/docs/guide-example/step-06-events.md +750 -0
- jac_client/docs/guide-example/step-07-effects.md +469 -0
- jac_client/docs/guide-example/step-08-walkers.md +534 -0
- jac_client/docs/guide-example/step-09-authentication.md +586 -0
- jac_client/docs/guide-example/step-10-routing.md +540 -0
- jac_client/docs/guide-example/step-11-final.md +964 -0
- jac_client/docs/imports.md +1142 -0
- jac_client/docs/lifecycle-hooks.md +774 -0
- jac_client/docs/routing.md +660 -0
- jac_client/examples/basic/.babelrc +9 -0
- jac_client/examples/basic/README.md +16 -0
- jac_client/examples/basic/app.jac +16 -0
- jac_client/examples/basic/package.json +27 -0
- jac_client/examples/basic/vite.config.js +28 -0
- jac_client/examples/basic-auth/.babelrc +9 -0
- jac_client/examples/basic-auth/README.md +16 -0
- jac_client/examples/basic-auth/app.jac +308 -0
- jac_client/examples/basic-auth/package.json +27 -0
- jac_client/examples/basic-auth/vite.config.js +28 -0
- jac_client/examples/basic-auth-with-router/.babelrc +9 -0
- jac_client/examples/basic-auth-with-router/README.md +60 -0
- jac_client/examples/basic-auth-with-router/app.jac +464 -0
- jac_client/examples/basic-auth-with-router/package.json +28 -0
- jac_client/examples/basic-auth-with-router/vite.config.js +28 -0
- jac_client/examples/basic-full-stack/.babelrc +9 -0
- jac_client/examples/basic-full-stack/README.md +18 -0
- jac_client/examples/basic-full-stack/app.jac +320 -0
- jac_client/examples/basic-full-stack/package.json +28 -0
- jac_client/examples/basic-full-stack/vite.config.js +28 -0
- jac_client/examples/full-stack-with-auth/.babelrc +9 -0
- jac_client/examples/full-stack-with-auth/README.md +16 -0
- jac_client/examples/full-stack-with-auth/app.jac +735 -0
- jac_client/examples/full-stack-with-auth/package.json +28 -0
- jac_client/examples/full-stack-with-auth/vite.config.js +30 -0
- jac_client/examples/little-x/app.jac +615 -0
- jac_client/examples/little-x/package.json +23 -0
- jac_client/examples/little-x/submit-button.jac +8 -0
- jac_client/examples/with-router/.babelrc +9 -0
- jac_client/examples/with-router/README.md +17 -0
- jac_client/examples/with-router/app.jac +323 -0
- jac_client/examples/with-router/package.json +28 -0
- jac_client/examples/with-router/vite.config.js +28 -0
- jac_client/plugin/cli.py +239 -0
- jac_client/plugin/client.py +89 -0
- jac_client/plugin/client_runtime.jac +234 -0
- jac_client/plugin/vite_client_bundle.py +355 -0
- jac_client/tests/__init__.py +2 -0
- jac_client/tests/fixtures/basic-app/app.jac +18 -0
- jac_client/tests/fixtures/client_app_with_antd/app.jac +28 -0
- jac_client/tests/fixtures/js_import/app.jac +30 -0
- jac_client/tests/fixtures/js_import/utils.js +22 -0
- jac_client/tests/fixtures/package-lock.json +329 -0
- jac_client/tests/fixtures/package.json +11 -0
- jac_client/tests/fixtures/relative_import/app.jac +13 -0
- jac_client/tests/fixtures/relative_import/button.jac +6 -0
- jac_client/tests/fixtures/spawn_test/app.jac +133 -0
- jac_client/tests/fixtures/test_fragments_spread/app.jac +53 -0
- jac_client/tests/test_cl.py +476 -0
- jac_client/tests/test_create_jac_app.py +139 -0
- jac_client-0.2.0.dist-info/METADATA +182 -0
- jac_client-0.2.0.dist-info/RECORD +72 -0
- jac_client-0.2.0.dist-info/WHEEL +4 -0
- jac_client-0.2.0.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
"""Tests for Vite client bundle generation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import json
|
|
7
|
+
import tempfile
|
|
8
|
+
import subprocess
|
|
9
|
+
|
|
10
|
+
from jaclang.runtimelib.machine import JacMachine as Jac
|
|
11
|
+
from jaclang.utils.test import TestCase
|
|
12
|
+
from jac_client.plugin.vite_client_bundle import ViteClientBundleBuilder
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ViteClientBundleBuilderTests(TestCase):
|
|
16
|
+
"""Validate Vite-powered client bundle compilation."""
|
|
17
|
+
|
|
18
|
+
def setUp(self) -> None:
|
|
19
|
+
Jac.reset_machine()
|
|
20
|
+
return super().setUp()
|
|
21
|
+
|
|
22
|
+
def tearDown(self) -> None:
|
|
23
|
+
Jac.reset_machine()
|
|
24
|
+
return super().tearDown()
|
|
25
|
+
|
|
26
|
+
def _create_test_project_with_vite(self, temp_path: Path, include_antd: bool = False) -> tuple[Path, Path]:
|
|
27
|
+
"""Create a minimal test project with Vite installed.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
temp_path: Path to the temporary directory
|
|
31
|
+
include_antd: If True, includes antd in dependencies
|
|
32
|
+
"""
|
|
33
|
+
# Create package.json with base dependencies
|
|
34
|
+
dependencies = {
|
|
35
|
+
"react": "^19.2.0",
|
|
36
|
+
"react-dom": "^19.2.0",
|
|
37
|
+
"react-router-dom": "^7.3.0"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Add antd if requested
|
|
41
|
+
if include_antd:
|
|
42
|
+
dependencies["antd"] = "^5.0.0"
|
|
43
|
+
|
|
44
|
+
# Create package.json structure
|
|
45
|
+
package_data = {
|
|
46
|
+
"name": "test-client",
|
|
47
|
+
"version": "0.0.1",
|
|
48
|
+
"type": "module",
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "npm run compile && vite build",
|
|
51
|
+
"dev": "vite dev",
|
|
52
|
+
"preview": "vite preview",
|
|
53
|
+
"compile": 'babel src --out-dir build --extensions ".jsx,.js" --out-file-extension .js'
|
|
54
|
+
},
|
|
55
|
+
"dependencies": dependencies,
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"vite": "^6.4.1",
|
|
58
|
+
"@babel/cli": "^7.28.3",
|
|
59
|
+
"@babel/core": "^7.28.5",
|
|
60
|
+
"@babel/preset-env": "^7.28.5",
|
|
61
|
+
"@babel/preset-react": "^7.28.5"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
package_json = temp_path / "package.json"
|
|
66
|
+
with package_json.open("w", encoding="utf-8") as f:
|
|
67
|
+
json.dump(package_data, f, indent=2)
|
|
68
|
+
|
|
69
|
+
# Create .babelrc file
|
|
70
|
+
babelrc = temp_path / ".babelrc"
|
|
71
|
+
babelrc.write_text("""{
|
|
72
|
+
"presets": [[
|
|
73
|
+
"@babel/preset-env",
|
|
74
|
+
{
|
|
75
|
+
"modules": false
|
|
76
|
+
}
|
|
77
|
+
], "@babel/preset-react"]
|
|
78
|
+
}
|
|
79
|
+
""", encoding="utf-8")
|
|
80
|
+
|
|
81
|
+
# Create vite.config.js file
|
|
82
|
+
vite_config = temp_path / "vite.config.js"
|
|
83
|
+
vite_config.write_text("""import { defineConfig } from "vite";
|
|
84
|
+
import path from "path";
|
|
85
|
+
import { fileURLToPath } from "url";
|
|
86
|
+
|
|
87
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
88
|
+
|
|
89
|
+
export default defineConfig({
|
|
90
|
+
root: ".",
|
|
91
|
+
build: {
|
|
92
|
+
rollupOptions: {
|
|
93
|
+
input: "build/main.js",
|
|
94
|
+
output: {
|
|
95
|
+
entryFileNames: "client.[hash].js",
|
|
96
|
+
assetFileNames: "[name].[ext]",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
outDir: "dist",
|
|
100
|
+
emptyOutDir: true,
|
|
101
|
+
minify: false,
|
|
102
|
+
},
|
|
103
|
+
publicDir: false,
|
|
104
|
+
resolve: {
|
|
105
|
+
alias: {
|
|
106
|
+
"@jac-client/utils": path.resolve(__dirname, "src/client_runtime.js"),
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
""", encoding="utf-8")
|
|
111
|
+
|
|
112
|
+
# Install dependencies
|
|
113
|
+
result = subprocess.run(
|
|
114
|
+
["npm", "install"],
|
|
115
|
+
cwd=temp_path,
|
|
116
|
+
check=False,
|
|
117
|
+
capture_output=True,
|
|
118
|
+
text=True,
|
|
119
|
+
)
|
|
120
|
+
if result.returncode != 0:
|
|
121
|
+
error_msg = f"npm install failed with exit code {result.returncode}\n"
|
|
122
|
+
error_msg += f"stdout: {result.stdout}\n"
|
|
123
|
+
error_msg += f"stderr: {result.stderr}\n"
|
|
124
|
+
raise RuntimeError(error_msg)
|
|
125
|
+
|
|
126
|
+
# Create output directory
|
|
127
|
+
output_dir = temp_path / "dist"
|
|
128
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
129
|
+
|
|
130
|
+
src_dir = temp_path / "src"
|
|
131
|
+
src_dir.mkdir(parents=True, exist_ok=True)
|
|
132
|
+
|
|
133
|
+
build_dir = temp_path / "build"
|
|
134
|
+
build_dir.mkdir(parents=True, exist_ok=True)
|
|
135
|
+
|
|
136
|
+
return package_json, output_dir
|
|
137
|
+
|
|
138
|
+
def test_build_bundle_with_vite(self) -> None:
|
|
139
|
+
"""Test that Vite bundling produces optimized output with proper structure."""
|
|
140
|
+
# Create a temporary directory for our test project
|
|
141
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
142
|
+
temp_path = Path(temp_dir)
|
|
143
|
+
|
|
144
|
+
package_json, output_dir = self._create_test_project_with_vite(temp_path)
|
|
145
|
+
runtime_path = Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
|
|
146
|
+
# Initialize the Vite builder
|
|
147
|
+
builder = ViteClientBundleBuilder(
|
|
148
|
+
runtime_path=runtime_path,
|
|
149
|
+
vite_package_json=package_json,
|
|
150
|
+
vite_output_dir=output_dir,
|
|
151
|
+
vite_minify=False, # Disable minification for easier inspection
|
|
152
|
+
)
|
|
153
|
+
# Import the test module
|
|
154
|
+
fixtures_dir = Path(__file__).parent / "fixtures" / "basic-app"
|
|
155
|
+
(module,) = Jac.jac_import("app", str(fixtures_dir))
|
|
156
|
+
# Build the bundle
|
|
157
|
+
bundle = builder.build(module, force=True)
|
|
158
|
+
|
|
159
|
+
self.assertIsNotNone(bundle)
|
|
160
|
+
self.assertEqual(bundle.module_name, "app")
|
|
161
|
+
self.assertIn("app", bundle.client_functions)
|
|
162
|
+
self.assertIn("ButtonProps", bundle.client_functions)
|
|
163
|
+
self.assertIn("API_LABEL", bundle.client_globals)
|
|
164
|
+
self.assertGreater(len(bundle.hash), 10)
|
|
165
|
+
|
|
166
|
+
# Verify bundle code contains expected content
|
|
167
|
+
self.assertIn("function app()", bundle.code)
|
|
168
|
+
self.assertIn('API_LABEL = "Runtime Test";', bundle.code)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# Verify bundle was written to output directory
|
|
172
|
+
bundle_files = list(output_dir.glob("client.*.js"))
|
|
173
|
+
self.assertGreater(len(bundle_files), 0, "Expected at least one bundle file")
|
|
174
|
+
|
|
175
|
+
# Verify cached bundle is identical
|
|
176
|
+
cached = builder.build(module, force=False)
|
|
177
|
+
self.assertEqual(bundle.hash, cached.hash)
|
|
178
|
+
self.assertEqual(bundle.code, cached.code)
|
|
179
|
+
|
|
180
|
+
def test_vite_bundle_without_package_json(self) -> None:
|
|
181
|
+
"""Test that missing package.json raises appropriate error."""
|
|
182
|
+
fixtures_dir = Path(__file__).parent / "fixtures" / "basic-app"
|
|
183
|
+
(module,) = Jac.jac_import("app", str(fixtures_dir))
|
|
184
|
+
|
|
185
|
+
runtime_path = Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
|
|
186
|
+
|
|
187
|
+
# Create builder without package.json
|
|
188
|
+
builder = ViteClientBundleBuilder(
|
|
189
|
+
runtime_path=runtime_path,
|
|
190
|
+
vite_package_json=Path("/nonexistent/package.json"),
|
|
191
|
+
vite_output_dir=Path("/tmp/output"),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# Building should raise an error
|
|
195
|
+
from jaclang.runtimelib.client_bundle import ClientBundleError
|
|
196
|
+
with self.assertRaises(ClientBundleError) as cm:
|
|
197
|
+
builder.build(module, force=True)
|
|
198
|
+
|
|
199
|
+
self.assertIn("Vite package.json not found", str(cm.exception))
|
|
200
|
+
|
|
201
|
+
def test_build_bundle_with_antd(self) -> None:
|
|
202
|
+
"""Test that Vite bundling works with Ant Design components."""
|
|
203
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
204
|
+
|
|
205
|
+
temp_path = Path(temp_dir)
|
|
206
|
+
|
|
207
|
+
# Create project with Vite and Ant Design installed
|
|
208
|
+
package_json, output_dir = self._create_test_project_with_vite(temp_path, include_antd=True)
|
|
209
|
+
runtime_path = Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
|
|
210
|
+
|
|
211
|
+
# Initialize the Vite builder
|
|
212
|
+
builder = ViteClientBundleBuilder(
|
|
213
|
+
runtime_path=runtime_path,
|
|
214
|
+
vite_package_json=package_json,
|
|
215
|
+
vite_output_dir=output_dir,
|
|
216
|
+
vite_minify=False,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Import the test module with Ant Design
|
|
220
|
+
fixtures_dir = Path(__file__).parent / "fixtures" / "client_app_with_antd"
|
|
221
|
+
(module,) = Jac.jac_import("app", str(fixtures_dir))
|
|
222
|
+
|
|
223
|
+
# Build the bundle
|
|
224
|
+
bundle = builder.build(module, force=True)
|
|
225
|
+
|
|
226
|
+
# Verify bundle structure
|
|
227
|
+
self.assertIsNotNone(bundle)
|
|
228
|
+
self.assertEqual(bundle.module_name, "app")
|
|
229
|
+
self.assertIn("ButtonTest", bundle.client_functions)
|
|
230
|
+
self.assertIn("CardTest", bundle.client_functions)
|
|
231
|
+
self.assertIn("APP_NAME", bundle.client_globals)
|
|
232
|
+
|
|
233
|
+
# Verify bundle code contains expected content
|
|
234
|
+
self.assertIn("function ButtonTest()", bundle.code)
|
|
235
|
+
self.assertIn("function CardTest()", bundle.code)
|
|
236
|
+
self.assertIn('APP_NAME = "Ant Design Test";', bundle.code)
|
|
237
|
+
|
|
238
|
+
# verify antd components are present
|
|
239
|
+
self.assertIn("ButtonGroup", bundle.code)
|
|
240
|
+
|
|
241
|
+
# Verify the Ant Design fixture content is present
|
|
242
|
+
self.assertIn("Testing Ant Design integration", bundle.code)
|
|
243
|
+
|
|
244
|
+
# Verify bundle was written to output directory
|
|
245
|
+
bundle_files = list(output_dir.glob("client.*.js"))
|
|
246
|
+
self.assertGreater(len(bundle_files), 0, "Expected at least one bundle file")
|
|
247
|
+
|
|
248
|
+
# Cleanup
|
|
249
|
+
builder.cleanup_temp_dir()
|
|
250
|
+
|
|
251
|
+
def test_relative_import(self) -> None:
|
|
252
|
+
"""Test that relative imports work correctly in Vite bundling."""
|
|
253
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
254
|
+
temp_path = Path(temp_dir)
|
|
255
|
+
|
|
256
|
+
# Create project with Vite installed
|
|
257
|
+
package_json, output_dir = self._create_test_project_with_vite(temp_path, include_antd=True)
|
|
258
|
+
runtime_path = Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
|
|
259
|
+
|
|
260
|
+
# Initialize the Vite builder
|
|
261
|
+
builder = ViteClientBundleBuilder(
|
|
262
|
+
runtime_path=runtime_path,
|
|
263
|
+
vite_package_json=package_json,
|
|
264
|
+
vite_output_dir=output_dir,
|
|
265
|
+
vite_minify=False,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Import the test module with relative import
|
|
269
|
+
fixtures_dir = Path(__file__).parent / "fixtures" / "relative_import"
|
|
270
|
+
(module,) = Jac.jac_import("app", str(fixtures_dir))
|
|
271
|
+
|
|
272
|
+
# Build the bundle
|
|
273
|
+
bundle = builder.build(module, force=True)
|
|
274
|
+
|
|
275
|
+
# Verify bundle structure
|
|
276
|
+
self.assertIsNotNone(bundle)
|
|
277
|
+
self.assertEqual(bundle.module_name, "app")
|
|
278
|
+
self.assertIn("RelativeImport", bundle.client_functions)
|
|
279
|
+
self.assertIn("app", bundle.client_functions)
|
|
280
|
+
self.assertIn("CustomButton", bundle.code)
|
|
281
|
+
|
|
282
|
+
# Verify bundle code contains expected content
|
|
283
|
+
self.assertIn("function RelativeImport()", bundle.code)
|
|
284
|
+
self.assertIn("function app()", bundle.code)
|
|
285
|
+
|
|
286
|
+
# Verify that the relative import (Button from .button) is properly resolved
|
|
287
|
+
self.assertIn("ButtonGroup", bundle.code)
|
|
288
|
+
|
|
289
|
+
# Verify bundle was written to output directory
|
|
290
|
+
bundle_files = list(output_dir.glob("client.*.js"))
|
|
291
|
+
self.assertGreater(len(bundle_files), 0, "Expected at least one bundle file")
|
|
292
|
+
|
|
293
|
+
# Cleanup
|
|
294
|
+
builder.cleanup_temp_dir()
|
|
295
|
+
|
|
296
|
+
def test_js_import(self) -> None:
|
|
297
|
+
"""Test that JavaScript file imports work correctly in Vite bundling."""
|
|
298
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
299
|
+
temp_path = Path(temp_dir)
|
|
300
|
+
# Create project with Vite installed
|
|
301
|
+
package_json, output_dir = self._create_test_project_with_vite(temp_path)
|
|
302
|
+
runtime_path = Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
|
|
303
|
+
# Initialize the Vite builder
|
|
304
|
+
builder = ViteClientBundleBuilder(
|
|
305
|
+
runtime_path=runtime_path,
|
|
306
|
+
vite_package_json=package_json,
|
|
307
|
+
vite_output_dir=output_dir,
|
|
308
|
+
vite_minify=False,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Import the test module with JavaScript import
|
|
312
|
+
fixtures_dir = Path(__file__).parent / "fixtures" / "js_import"
|
|
313
|
+
(module,) = Jac.jac_import("app", str(fixtures_dir))
|
|
314
|
+
|
|
315
|
+
# Build the bundle
|
|
316
|
+
bundle = builder.build(module, force=True)
|
|
317
|
+
|
|
318
|
+
# Verify bundle structure
|
|
319
|
+
self.assertIsNotNone(bundle)
|
|
320
|
+
self.assertEqual(bundle.module_name, "app")
|
|
321
|
+
self.assertIn("JsImportTest", bundle.client_functions)
|
|
322
|
+
self.assertIn("app", bundle.client_functions)
|
|
323
|
+
self.assertIn("JS_IMPORT_LABEL", bundle.client_globals)
|
|
324
|
+
|
|
325
|
+
# Verify bundle code contains expected content
|
|
326
|
+
self.assertIn("function JsImportTest()", bundle.code)
|
|
327
|
+
self.assertIn("function app()", bundle.code)
|
|
328
|
+
self.assertIn('JS_IMPORT_LABEL = "JavaScript Import Test";', bundle.code)
|
|
329
|
+
|
|
330
|
+
# Verify JavaScript imports are present in the bundle
|
|
331
|
+
# The JavaScript functions should be available in the bundle
|
|
332
|
+
self.assertIn("formatMessage", bundle.code)
|
|
333
|
+
self.assertIn("calculateSum", bundle.code)
|
|
334
|
+
self.assertIn("JS_CONSTANT", bundle.code)
|
|
335
|
+
self.assertIn("MessageFormatter", bundle.code)
|
|
336
|
+
|
|
337
|
+
# Verify the JavaScript utility code is included
|
|
338
|
+
self.assertIn("Hello,", bundle.code) # From formatMessage function
|
|
339
|
+
self.assertIn("Imported from JavaScript", bundle.code) # From JS_CONSTANT
|
|
340
|
+
|
|
341
|
+
# Verify bundle was written to output directory
|
|
342
|
+
bundle_files = list(output_dir.glob("client.*.js"))
|
|
343
|
+
self.assertGreater(len(bundle_files), 0, "Expected at least one bundle file")
|
|
344
|
+
|
|
345
|
+
# Cleanup
|
|
346
|
+
builder.cleanup_temp_dir()
|
|
347
|
+
|
|
348
|
+
def test_jsx_fragments_and_spread_props(self) -> None:
|
|
349
|
+
"""Test that JSX fragments and spread props work correctly."""
|
|
350
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
351
|
+
temp_path = Path(temp_dir)
|
|
352
|
+
|
|
353
|
+
# Create project with Vite installed
|
|
354
|
+
package_json, output_dir = self._create_test_project_with_vite(temp_path)
|
|
355
|
+
runtime_path = Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
|
|
356
|
+
|
|
357
|
+
# Initialize the Vite builder
|
|
358
|
+
builder = ViteClientBundleBuilder(
|
|
359
|
+
runtime_path=runtime_path,
|
|
360
|
+
vite_package_json=package_json,
|
|
361
|
+
vite_output_dir=output_dir,
|
|
362
|
+
vite_minify=False,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Import the test module with fragments and spread props
|
|
366
|
+
fixtures_dir = Path(__file__).parent / "fixtures" / "test_fragments_spread"
|
|
367
|
+
(module,) = Jac.jac_import("app", str(fixtures_dir))
|
|
368
|
+
|
|
369
|
+
# Build the bundle
|
|
370
|
+
bundle = builder.build(module, force=True)
|
|
371
|
+
|
|
372
|
+
# Verify bundle structure
|
|
373
|
+
self.assertIsNotNone(bundle)
|
|
374
|
+
self.assertEqual(bundle.module_name, "app")
|
|
375
|
+
self.assertIn("FragmentTest", bundle.client_functions)
|
|
376
|
+
self.assertIn("SpreadPropsTest", bundle.client_functions)
|
|
377
|
+
self.assertIn("MixedTest", bundle.client_functions)
|
|
378
|
+
self.assertIn("NestedFragments", bundle.client_functions)
|
|
379
|
+
|
|
380
|
+
# Verify spread props handling (Object.assign is used by compiler)
|
|
381
|
+
self.assertIn("Object.assign", bundle.code)
|
|
382
|
+
|
|
383
|
+
# Verify fragment test function exists
|
|
384
|
+
self.assertIn("function FragmentTest()", bundle.code)
|
|
385
|
+
|
|
386
|
+
# Verify spread props test function exists
|
|
387
|
+
self.assertIn("function SpreadPropsTest()", bundle.code)
|
|
388
|
+
|
|
389
|
+
# Verify bundle was written to output directory
|
|
390
|
+
bundle_files = list(output_dir.glob("client.*.js"))
|
|
391
|
+
self.assertGreater(len(bundle_files), 0, "Expected at least one bundle file")
|
|
392
|
+
|
|
393
|
+
# Cleanup
|
|
394
|
+
builder.cleanup_temp_dir()
|
|
395
|
+
|
|
396
|
+
def test_spawn_operator(self) -> None:
|
|
397
|
+
"""Test that spawn operator generates correct __jacSpawn calls for both orderings and node types (root and UUID)."""
|
|
398
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
399
|
+
temp_path = Path(temp_dir)
|
|
400
|
+
|
|
401
|
+
# Create project with Vite installed
|
|
402
|
+
package_json, output_dir = self._create_test_project_with_vite(temp_path)
|
|
403
|
+
runtime_path = Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
|
|
404
|
+
|
|
405
|
+
# Initialize the Vite builder
|
|
406
|
+
builder = ViteClientBundleBuilder(
|
|
407
|
+
runtime_path=runtime_path,
|
|
408
|
+
vite_package_json=package_json,
|
|
409
|
+
vite_output_dir=output_dir,
|
|
410
|
+
vite_minify=False,
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
# Import the test module with both spawn operator orderings
|
|
414
|
+
fixtures_dir = Path(__file__).parent / "fixtures" / "spawn_test"
|
|
415
|
+
(module,) = Jac.jac_import("app", str(fixtures_dir))
|
|
416
|
+
|
|
417
|
+
# Build the bundle
|
|
418
|
+
bundle = builder.build(module, force=True)
|
|
419
|
+
|
|
420
|
+
# Verify bundle structure
|
|
421
|
+
self.assertIsNotNone(bundle)
|
|
422
|
+
self.assertEqual(bundle.module_name, "app")
|
|
423
|
+
self.assertIn("app", bundle.client_functions)
|
|
424
|
+
|
|
425
|
+
# Verify complete __jacSpawn calls for root spawn scenarios
|
|
426
|
+
# Standard order: root spawn test_walker()
|
|
427
|
+
self.assertIn('__jacSpawn("test_walker", "", {})', bundle.code)
|
|
428
|
+
|
|
429
|
+
# Standard order: root spawn parameterized_walker(value=42)
|
|
430
|
+
self.assertIn('__jacSpawn("parameterized_walker", "", {', bundle.code)
|
|
431
|
+
self.assertIn('"value": 42', bundle.code)
|
|
432
|
+
|
|
433
|
+
# Reverse order: test_walker(message="Reverse spawn!") spawn root
|
|
434
|
+
# Should generate: __jacSpawn("test_walker", "", {message: "Reverse spawn!"})
|
|
435
|
+
self.assertRegex(
|
|
436
|
+
bundle.code,
|
|
437
|
+
r'__jacSpawn\("test_walker",\s*"",\s*\{[^}]*"message":\s*"Reverse spawn!"[^}]*\}\)'
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
# Verify UUID spawn scenarios with complete calls
|
|
441
|
+
# Standard UUID spawn: node_id spawn test_walker()
|
|
442
|
+
# Should generate: __jacSpawn("test_walker", node_id, {})
|
|
443
|
+
self.assertIn('__jacSpawn("test_walker", node_id, {})', bundle.code)
|
|
444
|
+
self.assertIn('"550e8400-e29b-41d4-a716-446655440000"', bundle.code)
|
|
445
|
+
|
|
446
|
+
# Reverse UUID spawn: parameterized_walker(value=100) spawn another_node_id
|
|
447
|
+
# Should generate: __jacSpawn("parameterized_walker", another_node_id, {value: 100})
|
|
448
|
+
self.assertRegex(
|
|
449
|
+
bundle.code,
|
|
450
|
+
r'__jacSpawn\("parameterized_walker",\s*another_node_id,\s*\{[^}]*"value":\s*100[^}]*\}\)'
|
|
451
|
+
)
|
|
452
|
+
self.assertIn('"6ba7b810-9dad-11d1-80b4-00c04fd430c8"', bundle.code)
|
|
453
|
+
|
|
454
|
+
# Verify positional argument mapping for walkers
|
|
455
|
+
self.assertRegex(
|
|
456
|
+
bundle.code,
|
|
457
|
+
r'__jacSpawn\("positional_walker",\s*node_id,\s*\{[^}]*"label":\s*"Node positional"[^}]*"count":\s*2',
|
|
458
|
+
)
|
|
459
|
+
# Verify spread (**kwargs) handling when walker is on left-hand side
|
|
460
|
+
self.assertRegex(
|
|
461
|
+
bundle.code,
|
|
462
|
+
r'__jacSpawn\("positional_walker",\s*"",\s*_objectSpread\(\{\s*"label":\s*"Spread order"[^}]*"count":\s*5\s*\},\s*extra_fields\)',
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
# Verify we have at least 7 __jacSpawn calls (previous cases + new positional/spread)
|
|
466
|
+
self.assertTrue(
|
|
467
|
+
bundle.code.count('__jacSpawn') >= 7,
|
|
468
|
+
"Expected at least 7 __jacSpawn calls in bundle"
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
# Verify bundle was written to output directory
|
|
472
|
+
bundle_files = list(output_dir.glob("client.*.js"))
|
|
473
|
+
self.assertGreater(len(bundle_files), 0, "Expected at least one bundle file")
|
|
474
|
+
|
|
475
|
+
# Cleanup
|
|
476
|
+
builder.cleanup_temp_dir()
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Test create-jac-app command."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
from subprocess import run
|
|
7
|
+
from unittest import TestCase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestCreateJacApp(TestCase):
|
|
11
|
+
"""Test create-jac-app command functionality."""
|
|
12
|
+
|
|
13
|
+
def test_create_jac_app(self) -> None:
|
|
14
|
+
"""Test create-jac-app command."""
|
|
15
|
+
test_project_name = "test-jac-app"
|
|
16
|
+
|
|
17
|
+
# Create a temporary directory for testing
|
|
18
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
19
|
+
original_cwd = os.getcwd()
|
|
20
|
+
try:
|
|
21
|
+
# Change to temp directory
|
|
22
|
+
os.chdir(temp_dir)
|
|
23
|
+
|
|
24
|
+
# Run create-jac-app command
|
|
25
|
+
result = run(
|
|
26
|
+
[
|
|
27
|
+
"jac",
|
|
28
|
+
"create_jac_app",
|
|
29
|
+
test_project_name
|
|
30
|
+
],
|
|
31
|
+
capture_output=True,
|
|
32
|
+
text=True,
|
|
33
|
+
check=True
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Check that command succeeded
|
|
37
|
+
self.assertEqual(result.returncode, 0)
|
|
38
|
+
self.assertIn(f"Successfully created Jac application '{test_project_name}'!", result.stdout)
|
|
39
|
+
|
|
40
|
+
# Verify project directory was created
|
|
41
|
+
project_path = os.path.join(temp_dir, test_project_name)
|
|
42
|
+
self.assertTrue(os.path.exists(project_path))
|
|
43
|
+
self.assertTrue(os.path.isdir(project_path))
|
|
44
|
+
|
|
45
|
+
# Verify package.json was created and has correct content
|
|
46
|
+
package_json_path = os.path.join(project_path, "package.json")
|
|
47
|
+
self.assertTrue(os.path.exists(package_json_path))
|
|
48
|
+
|
|
49
|
+
with open(package_json_path, "r") as f:
|
|
50
|
+
package_data = json.load(f)
|
|
51
|
+
|
|
52
|
+
self.assertEqual(package_data["name"], test_project_name)
|
|
53
|
+
self.assertEqual(package_data["type"], "module")
|
|
54
|
+
self.assertIn("vite", package_data["devDependencies"])
|
|
55
|
+
self.assertIn("build", package_data["scripts"])
|
|
56
|
+
self.assertIn("dev", package_data["scripts"])
|
|
57
|
+
self.assertIn("preview", package_data["scripts"])
|
|
58
|
+
|
|
59
|
+
# Verify app.jac file was created
|
|
60
|
+
app_jac_path = os.path.join(project_path, "app.jac")
|
|
61
|
+
self.assertTrue(os.path.exists(app_jac_path))
|
|
62
|
+
|
|
63
|
+
with open(app_jac_path, "r") as f:
|
|
64
|
+
app_jac_content = f.read()
|
|
65
|
+
|
|
66
|
+
self.assertIn("app()", app_jac_content)
|
|
67
|
+
|
|
68
|
+
# Verify README.md was created
|
|
69
|
+
readme_path = os.path.join(project_path, "README.md")
|
|
70
|
+
self.assertTrue(os.path.exists(readme_path))
|
|
71
|
+
|
|
72
|
+
with open(readme_path, "r") as f:
|
|
73
|
+
readme_content = f.read()
|
|
74
|
+
|
|
75
|
+
self.assertIn(f"# {test_project_name}", readme_content)
|
|
76
|
+
self.assertIn("jac serve app.jac", readme_content)
|
|
77
|
+
|
|
78
|
+
# Verify node_modules was created (npm install ran)
|
|
79
|
+
node_modules_path = os.path.join(project_path, "node_modules")
|
|
80
|
+
self.assertTrue(os.path.exists(node_modules_path))
|
|
81
|
+
|
|
82
|
+
finally:
|
|
83
|
+
# Return to original directory
|
|
84
|
+
os.chdir(original_cwd)
|
|
85
|
+
|
|
86
|
+
def test_create_jac_app_invalid_name(self) -> None:
|
|
87
|
+
"""Test create-jac-app command with invalid project name."""
|
|
88
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
89
|
+
original_cwd = os.getcwd()
|
|
90
|
+
try:
|
|
91
|
+
os.chdir(temp_dir)
|
|
92
|
+
|
|
93
|
+
# Test with invalid name containing spaces
|
|
94
|
+
result = run(
|
|
95
|
+
[
|
|
96
|
+
"jac",
|
|
97
|
+
"create_jac_app",
|
|
98
|
+
"invalid name with spaces"
|
|
99
|
+
],
|
|
100
|
+
capture_output=True,
|
|
101
|
+
text=True
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Should fail with non-zero exit code
|
|
105
|
+
self.assertNotEqual(result.returncode, 0)
|
|
106
|
+
self.assertIn("Project name must contain only letters, numbers, hyphens, and underscores", result.stderr)
|
|
107
|
+
|
|
108
|
+
finally:
|
|
109
|
+
os.chdir(original_cwd)
|
|
110
|
+
|
|
111
|
+
def test_create_jac_app_existing_directory(self) -> None:
|
|
112
|
+
"""Test create-jac-app command when directory already exists."""
|
|
113
|
+
test_project_name = "existing-test-app"
|
|
114
|
+
|
|
115
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
116
|
+
original_cwd = os.getcwd()
|
|
117
|
+
try:
|
|
118
|
+
os.chdir(temp_dir)
|
|
119
|
+
|
|
120
|
+
# Create the directory first
|
|
121
|
+
os.makedirs(test_project_name)
|
|
122
|
+
|
|
123
|
+
# Try to create app with same name
|
|
124
|
+
result = run(
|
|
125
|
+
[
|
|
126
|
+
"jac",
|
|
127
|
+
"create_jac_app",
|
|
128
|
+
test_project_name
|
|
129
|
+
],
|
|
130
|
+
capture_output=True,
|
|
131
|
+
text=True
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Should fail with non-zero exit code
|
|
135
|
+
self.assertNotEqual(result.returncode, 0)
|
|
136
|
+
self.assertIn(f"Directory '{test_project_name}' already exists", result.stderr)
|
|
137
|
+
|
|
138
|
+
finally:
|
|
139
|
+
os.chdir(original_cwd)
|