jac-client 0.2.0__py3-none-any.whl → 0.2.2__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 (154) hide show
  1. jac_client/docs/README.md +50 -20
  2. jac_client/docs/advanced-state.md +13 -14
  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/file-system/app.jac.md +121 -0
  6. jac_client/docs/file-system/backend-frontend.md +217 -0
  7. jac_client/docs/file-system/intro.md +72 -0
  8. jac_client/docs/file-system/nested-imports.md +348 -0
  9. jac_client/docs/guide-example/intro.md +11 -13
  10. jac_client/docs/guide-example/step-01-setup.md +30 -20
  11. jac_client/docs/guide-example/step-02-components.md +24 -24
  12. jac_client/docs/guide-example/step-03-styling.md +24 -24
  13. jac_client/docs/guide-example/step-04-todo-ui.md +17 -17
  14. jac_client/docs/guide-example/step-05-local-state.md +23 -23
  15. jac_client/docs/guide-example/step-06-events.md +23 -24
  16. jac_client/docs/guide-example/step-07-effects.md +27 -28
  17. jac_client/docs/guide-example/step-08-walkers.md +23 -23
  18. jac_client/docs/guide-example/step-09-authentication.md +18 -18
  19. jac_client/docs/guide-example/step-10-routing.md +20 -21
  20. jac_client/docs/guide-example/step-11-final.md +34 -35
  21. jac_client/docs/imports.md +4 -5
  22. jac_client/docs/lifecycle-hooks.md +12 -13
  23. jac_client/docs/routing.md +21 -22
  24. jac_client/docs/styling/intro.md +249 -0
  25. jac_client/docs/styling/js-styling.md +367 -0
  26. jac_client/docs/styling/material-ui.md +341 -0
  27. jac_client/docs/styling/pure-css.md +299 -0
  28. jac_client/docs/styling/sass.md +403 -0
  29. jac_client/docs/styling/styled-components.md +395 -0
  30. jac_client/docs/styling/tailwind.md +298 -0
  31. jac_client/examples/all-in-one/.babelrc +9 -0
  32. jac_client/examples/all-in-one/README.md +16 -0
  33. jac_client/examples/all-in-one/app.jac +426 -0
  34. jac_client/examples/all-in-one/assets/burger.png +0 -0
  35. jac_client/examples/all-in-one/button.jac +7 -0
  36. jac_client/examples/all-in-one/components/button.jac +7 -0
  37. jac_client/examples/all-in-one/package.json +29 -0
  38. jac_client/examples/all-in-one/styles.css +26 -0
  39. jac_client/examples/all-in-one/vite.config.js +28 -0
  40. jac_client/examples/asset-serving/css-with-image/.babelrc +9 -0
  41. jac_client/examples/asset-serving/css-with-image/README.md +91 -0
  42. jac_client/examples/asset-serving/css-with-image/app.jac +88 -0
  43. jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
  44. jac_client/examples/asset-serving/css-with-image/package.json +28 -0
  45. jac_client/examples/asset-serving/css-with-image/styles.css +26 -0
  46. jac_client/examples/asset-serving/css-with-image/vite.config.js +28 -0
  47. jac_client/examples/asset-serving/image-asset/.babelrc +9 -0
  48. jac_client/examples/asset-serving/image-asset/README.md +119 -0
  49. jac_client/examples/asset-serving/image-asset/app.jac +55 -0
  50. jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
  51. jac_client/examples/asset-serving/image-asset/package.json +28 -0
  52. jac_client/examples/asset-serving/image-asset/styles.css +26 -0
  53. jac_client/examples/asset-serving/image-asset/vite.config.js +28 -0
  54. jac_client/examples/asset-serving/import-alias/.babelrc +9 -0
  55. jac_client/examples/asset-serving/import-alias/README.md +83 -0
  56. jac_client/examples/asset-serving/import-alias/app.jac +111 -0
  57. jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
  58. jac_client/examples/asset-serving/import-alias/package.json +28 -0
  59. jac_client/examples/asset-serving/import-alias/vite.config.js +28 -0
  60. jac_client/examples/basic/app.jac +14 -9
  61. jac_client/examples/basic/package.json +1 -1
  62. jac_client/examples/basic/vite.config.js +0 -1
  63. jac_client/examples/basic-auth/package.json +1 -1
  64. jac_client/examples/basic-auth/vite.config.js +0 -1
  65. jac_client/examples/basic-auth-with-router/package.json +1 -1
  66. jac_client/examples/basic-auth-with-router/vite.config.js +0 -1
  67. jac_client/examples/basic-full-stack/package.json +1 -1
  68. jac_client/examples/basic-full-stack/vite.config.js +0 -1
  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 +84 -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 +27 -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 +122 -0
  78. jac_client/examples/css-styling/material-ui/package.json +32 -0
  79. jac_client/examples/css-styling/material-ui/vite.config.js +27 -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 +64 -0
  83. jac_client/examples/css-styling/pure-css/package.json +28 -0
  84. jac_client/examples/css-styling/pure-css/styles.css +111 -0
  85. jac_client/examples/css-styling/pure-css/vite.config.js +27 -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 +64 -0
  89. jac_client/examples/css-styling/sass-example/package.json +29 -0
  90. jac_client/examples/css-styling/sass-example/styles.scss +153 -0
  91. jac_client/examples/css-styling/sass-example/vite.config.js +27 -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 +71 -0
  95. jac_client/examples/css-styling/styled-components/package.json +29 -0
  96. jac_client/examples/css-styling/styled-components/styled.js +90 -0
  97. jac_client/examples/css-styling/styled-components/vite.config.js +27 -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 +63 -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 +29 -0
  104. jac_client/examples/full-stack-with-auth/app.jac +20 -33
  105. jac_client/examples/full-stack-with-auth/package.json +1 -1
  106. jac_client/examples/full-stack-with-auth/vite.config.js +0 -1
  107. jac_client/examples/little-x/app.jac +327 -218
  108. jac_client/examples/little-x/submit-button.jac +1 -1
  109. jac_client/examples/nested-folders/nested-advance/.babelrc +9 -0
  110. jac_client/examples/nested-folders/nested-advance/ButtonRoot.jac +11 -0
  111. jac_client/examples/nested-folders/nested-advance/README.md +77 -0
  112. jac_client/examples/nested-folders/nested-advance/app.jac +35 -0
  113. jac_client/examples/nested-folders/nested-advance/level1/ButtonSecondL.jac +19 -0
  114. jac_client/examples/nested-folders/nested-advance/level1/Card.jac +43 -0
  115. jac_client/examples/nested-folders/nested-advance/level1/level2/ButtonThirdL.jac +25 -0
  116. jac_client/examples/nested-folders/nested-advance/package.json +29 -0
  117. jac_client/examples/nested-folders/nested-advance/vite.config.js +28 -0
  118. jac_client/examples/nested-folders/nested-basic/.babelrc +9 -0
  119. jac_client/examples/nested-folders/nested-basic/README.md +183 -0
  120. jac_client/examples/nested-folders/nested-basic/app.jac +13 -0
  121. jac_client/examples/nested-folders/nested-basic/app.js +7 -0
  122. jac_client/examples/nested-folders/nested-basic/button.jac +7 -0
  123. jac_client/examples/nested-folders/nested-basic/components/button.jac +7 -0
  124. jac_client/examples/nested-folders/nested-basic/package.json +28 -0
  125. jac_client/examples/nested-folders/nested-basic/vite.config.js +27 -0
  126. jac_client/examples/with-router/app.jac +1 -1
  127. jac_client/examples/with-router/package.json +1 -1
  128. jac_client/examples/with-router/vite.config.js +0 -1
  129. jac_client/plugin/cli.py +7 -2
  130. jac_client/plugin/client.py +68 -5
  131. jac_client/plugin/client_runtime.jac +1 -1
  132. jac_client/plugin/vite_client_bundle.py +162 -14
  133. jac_client/tests/__init__.py +0 -1
  134. jac_client/tests/fixtures/basic-app/app.jac +7 -2
  135. jac_client/tests/fixtures/cl_file/app.cl.jac +48 -0
  136. jac_client/tests/fixtures/cl_file/app.jac +15 -0
  137. jac_client/tests/fixtures/client_app_with_antd/app.jac +14 -8
  138. jac_client/tests/fixtures/js_import/app.jac +19 -15
  139. jac_client/tests/fixtures/js_import/utils.js +0 -1
  140. jac_client/tests/fixtures/package.json +1 -1
  141. jac_client/tests/fixtures/relative_import/app.jac +4 -6
  142. jac_client/tests/fixtures/relative_import/button.jac +7 -6
  143. jac_client/tests/fixtures/spawn_test/app.jac +1 -5
  144. jac_client/tests/fixtures/test_fragments_spread/app.jac +24 -10
  145. jac_client/tests/test_asset_examples.py +322 -0
  146. jac_client/tests/test_cl.py +480 -426
  147. jac_client/tests/test_create_jac_app.py +125 -133
  148. jac_client/tests/test_it.py +329 -0
  149. jac_client/tests/test_nested_file.py +374 -0
  150. {jac_client-0.2.0.dist-info → jac_client-0.2.2.dist-info}/METADATA +2 -2
  151. jac_client-0.2.2.dist-info/RECORD +171 -0
  152. jac_client-0.2.0.dist-info/RECORD +0 -72
  153. {jac_client-0.2.0.dist-info → jac_client-0.2.2.dist-info}/WHEEL +0 -0
  154. {jac_client-0.2.0.dist-info → jac_client-0.2.2.dist-info}/entry_points.txt +0 -0
@@ -3,26 +3,37 @@
3
3
  cl def FragmentTest() {
4
4
  # Test that fragments work
5
5
  return <>
6
- <h1>{"Fragment Title"}</h1>
7
- <p>{"Fragment content"}</p>
6
+ <h1>
7
+ {"Fragment Title"}
8
+ </h1>
9
+ <p>
10
+ {"Fragment content"}
11
+ </p>
8
12
  </>;
9
13
  }
10
14
 
11
15
  cl def SpreadPropsTest() {
12
16
  # Test spread props
13
17
  unwrapped = {"id": "my-div", "class": "container", "data-role": "main"};
14
-
15
- return <div {...unwrapped}>
16
- <span>{"Spread props work!"}</span>
18
+
19
+ return <div
20
+ {...unwrapped}
21
+ >
22
+ <span>
23
+ {"Spread props work!"}
24
+ </span>
17
25
  </div>;
18
26
  }
19
27
 
20
28
  cl def MixedTest() {
21
29
  # Test mixing spread props with regular props
22
30
  baseStyle = {"id": "base-id", "color": "blue"};
23
-
31
+
24
32
  return <>
25
- <div {...baseStyle} class="override">
33
+ <div
34
+ {...baseStyle}
35
+ class="override"
36
+ >
26
37
  {"Mixed test"}
27
38
  </div>
28
39
  <div class="normal">
@@ -34,10 +45,14 @@ cl def MixedTest() {
34
45
  cl def NestedFragments() {
35
46
  return <div class="wrapper">
36
47
  <>
37
- <h2>{"Nested Fragment 1"}</h2>
48
+ <h2>
49
+ {"Nested Fragment 1"}
50
+ </h2>
38
51
  </>
39
52
  <>
40
- <p>{"Nested Fragment 2"}</p>
53
+ <p>
54
+ {"Nested Fragment 2"}
55
+ </p>
41
56
  </>
42
57
  </div>;
43
58
  }
@@ -50,4 +65,3 @@ cl def app() {
50
65
  <NestedFragments />
51
66
  </div>;
52
67
  }
53
-
@@ -0,0 +1,322 @@
1
+ """Tests for asset-serving and css-styling examples."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import shutil
7
+ import subprocess
8
+ import tempfile
9
+ from pathlib import Path
10
+
11
+ import pytest
12
+
13
+ from jac_client.plugin.vite_client_bundle import ViteClientBundleBuilder
14
+ from jaclang.runtimelib.runtime import JacRuntime as Jac
15
+
16
+
17
+ @pytest.fixture(autouse=True)
18
+ def reset_jac_machine():
19
+ """Reset Jac machine before and after each test."""
20
+ Jac.reset_machine()
21
+ yield
22
+ Jac.reset_machine()
23
+
24
+
25
+ def _create_test_project_with_vite(
26
+ temp_path: Path, include_assets: bool = False
27
+ ) -> tuple[Path, Path]:
28
+ """Create a minimal test project with Vite installed.
29
+
30
+ Args:
31
+ temp_path: Path to the temporary directory
32
+ include_assets: If True, includes asset handling setup
33
+ """
34
+ # Create package.json with base dependencies
35
+ package_data = {
36
+ "name": "test-client",
37
+ "version": "0.0.1",
38
+ "type": "module",
39
+ "scripts": {
40
+ "build": "npm run compile && vite build",
41
+ "dev": "vite dev",
42
+ "preview": "vite preview",
43
+ "compile": 'babel src --out-dir build --extensions ".jsx,.js" --out-file-extension .js',
44
+ },
45
+ "dependencies": {
46
+ "react": "^19.2.0",
47
+ "react-dom": "^19.2.0",
48
+ "react-router-dom": "^7.3.0",
49
+ },
50
+ "devDependencies": {
51
+ "vite": "^6.4.1",
52
+ "@babel/cli": "^7.28.3",
53
+ "@babel/core": "^7.28.5",
54
+ "@babel/preset-env": "^7.28.5",
55
+ "@babel/preset-react": "^7.28.5",
56
+ },
57
+ }
58
+
59
+ package_json = temp_path / "package.json"
60
+ with package_json.open("w", encoding="utf-8") as f:
61
+ json.dump(package_data, f, indent=2)
62
+
63
+ # Create .babelrc file
64
+ babelrc = temp_path / ".babelrc"
65
+ babelrc.write_text(
66
+ """{
67
+ "presets": [[
68
+ "@babel/preset-env",
69
+ {
70
+ "modules": false
71
+ }
72
+ ], "@babel/preset-react"]
73
+ }
74
+ """,
75
+ encoding="utf-8",
76
+ )
77
+
78
+ # Create vite.config.js file
79
+ vite_config = temp_path / "vite.config.js"
80
+ if include_assets:
81
+ vite_config.write_text(
82
+ """import { defineConfig } from "vite";
83
+ import path from "path";
84
+ import { fileURLToPath } from "url";
85
+
86
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
87
+
88
+ export default defineConfig({
89
+ root: ".",
90
+ build: {
91
+ rollupOptions: {
92
+ input: "build/main.js",
93
+ output: {
94
+ entryFileNames: "client.[hash].js",
95
+ assetFileNames: "[name].[ext]",
96
+ },
97
+ },
98
+ outDir: "dist",
99
+ emptyOutDir: true,
100
+ minify: false,
101
+ },
102
+ publicDir: false,
103
+ resolve: {
104
+ alias: {
105
+ "@jac-client/utils": path.resolve(__dirname, "src/client_runtime.js"),
106
+ "@jac-client/assets": path.resolve(__dirname, "src/assets"),
107
+ },
108
+ },
109
+ });
110
+ """,
111
+ encoding="utf-8",
112
+ )
113
+ else:
114
+ vite_config.write_text(
115
+ """import { defineConfig } from "vite";
116
+ import path from "path";
117
+ import { fileURLToPath } from "url";
118
+
119
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
120
+
121
+ export default defineConfig({
122
+ root: ".",
123
+ build: {
124
+ rollupOptions: {
125
+ input: "build/main.js",
126
+ output: {
127
+ entryFileNames: "client.[hash].js",
128
+ assetFileNames: "[name].[ext]",
129
+ },
130
+ },
131
+ outDir: "dist",
132
+ emptyOutDir: true,
133
+ minify: false,
134
+ },
135
+ publicDir: false,
136
+ resolve: {
137
+ alias: {
138
+ "@jac-client/utils": path.resolve(__dirname, "src/client_runtime.js"),
139
+ },
140
+ },
141
+ });
142
+ """,
143
+ encoding="utf-8",
144
+ )
145
+
146
+ # Install dependencies
147
+ result = subprocess.run(
148
+ ["npm", "install"],
149
+ cwd=temp_path,
150
+ check=False,
151
+ capture_output=True,
152
+ text=True,
153
+ )
154
+ if result.returncode != 0:
155
+ error_msg = f"npm install failed with exit code {result.returncode}\n"
156
+ error_msg += f"stdout: {result.stdout}\n"
157
+ error_msg += f"stderr: {result.stderr}\n"
158
+ raise RuntimeError(error_msg)
159
+
160
+ # Create output directory
161
+ output_dir = temp_path / "dist"
162
+ output_dir.mkdir(parents=True, exist_ok=True)
163
+
164
+ src_dir = temp_path / "src"
165
+ src_dir.mkdir(parents=True, exist_ok=True)
166
+
167
+ build_dir = temp_path / "build"
168
+ build_dir.mkdir(parents=True, exist_ok=True)
169
+
170
+ return package_json, output_dir
171
+
172
+
173
+ def test_image_asset_example() -> None:
174
+ """Test image-asset example with static asset paths."""
175
+ with tempfile.TemporaryDirectory() as temp_dir:
176
+ temp_path = Path(temp_dir)
177
+
178
+ package_json, output_dir = _create_test_project_with_vite(
179
+ temp_path, include_assets=True
180
+ )
181
+ runtime_path = Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
182
+
183
+ # Initialize the Vite builder
184
+ builder = ViteClientBundleBuilder(
185
+ runtime_path=runtime_path,
186
+ vite_package_json=package_json,
187
+ vite_output_dir=output_dir,
188
+ vite_minify=False,
189
+ )
190
+
191
+ # Import the image-asset example
192
+ examples_dir = (
193
+ Path(__file__).parent.parent / "examples" / "asset-serving" / "image-asset"
194
+ )
195
+ (module,) = Jac.jac_import("app", str(examples_dir))
196
+
197
+ # Build the bundle
198
+ bundle = builder.build(module, force=True)
199
+
200
+ # Verify bundle structure
201
+ assert bundle is not None
202
+ assert bundle.module_name == "app"
203
+ assert "app" in bundle.client_functions
204
+
205
+ # Verify image path is in the bundle
206
+ assert "/static/assets/burger.png" in bundle.code
207
+
208
+ # Verify bundle was written to output directory
209
+ bundle_files = list(output_dir.glob("client.*.js"))
210
+ assert len(bundle_files) > 0, "Expected at least one bundle file"
211
+
212
+ # Cleanup
213
+ builder.cleanup_temp_dir()
214
+
215
+
216
+ def test_css_with_image_example() -> None:
217
+ """Test css-with-image example with CSS and image assets."""
218
+ with tempfile.TemporaryDirectory() as temp_dir:
219
+ temp_path = Path(temp_dir)
220
+
221
+ package_json, output_dir = _create_test_project_with_vite(
222
+ temp_path, include_assets=True
223
+ )
224
+ runtime_path = Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
225
+
226
+ # Initialize the Vite builder
227
+ builder = ViteClientBundleBuilder(
228
+ runtime_path=runtime_path,
229
+ vite_package_json=package_json,
230
+ vite_output_dir=output_dir,
231
+ vite_minify=False,
232
+ )
233
+
234
+ # Import the css-with-image example
235
+ examples_dir = (
236
+ Path(__file__).parent.parent
237
+ / "examples"
238
+ / "asset-serving"
239
+ / "css-with-image"
240
+ )
241
+ (module,) = Jac.jac_import("app", str(examples_dir))
242
+
243
+ # Build the bundle
244
+ bundle = builder.build(module, force=True)
245
+
246
+ # Verify bundle structure
247
+ assert bundle is not None
248
+ assert bundle.module_name == "app"
249
+ assert "app" in bundle.client_functions
250
+
251
+ # Verify CSS import is present (CSS should be extracted to separate file)
252
+ # The bundle should reference the CSS file
253
+ assert "import" in bundle.code.lower()
254
+
255
+ # Verify image paths are in the bundle
256
+ assert "/static/assets/burger.png" in bundle.code
257
+
258
+ # Verify CSS file was extracted
259
+ css_files = list(output_dir.glob("*.css"))
260
+ assert len(css_files) > 0, "Expected at least one CSS file"
261
+
262
+ # Verify bundle was written to output directory
263
+ bundle_files = list(output_dir.glob("client.*.js"))
264
+ assert len(bundle_files) > 0, "Expected at least one bundle file"
265
+
266
+ # Cleanup
267
+ builder.cleanup_temp_dir()
268
+
269
+
270
+ def test_import_alias_example() -> None:
271
+ """Test import-alias example with @jac-client/assets alias."""
272
+ with tempfile.TemporaryDirectory() as temp_dir:
273
+ temp_path = Path(temp_dir)
274
+ package_json, output_dir = _create_test_project_with_vite(
275
+ temp_path, include_assets=True
276
+ )
277
+ runtime_path = Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
278
+
279
+ # Initialize the Vite builder
280
+ builder = ViteClientBundleBuilder(
281
+ runtime_path=runtime_path,
282
+ vite_package_json=package_json,
283
+ vite_output_dir=output_dir,
284
+ vite_minify=False,
285
+ )
286
+
287
+ # Import the import-alias example
288
+ examples_dir = (
289
+ Path(__file__).parent.parent / "examples" / "asset-serving" / "import-alias"
290
+ )
291
+ (module,) = Jac.jac_import("app", str(examples_dir))
292
+
293
+ # Copy assets from example directory to temp project's src/assets/
294
+ # This is needed because @jac-client/assets alias points to src/assets
295
+ example_assets_dir = examples_dir / "assets"
296
+ temp_assets_dir = temp_path / "src" / "assets"
297
+ if example_assets_dir.exists():
298
+ temp_assets_dir.mkdir(parents=True, exist_ok=True)
299
+ # Copy all files from example assets to temp assets
300
+ for asset_file in example_assets_dir.iterdir():
301
+ if asset_file.is_file():
302
+ shutil.copy2(asset_file, temp_assets_dir / asset_file.name)
303
+
304
+ # Build the bundle
305
+ bundle = builder.build(module, force=True)
306
+
307
+ # Verify bundle structure
308
+ assert bundle is not None
309
+ assert bundle.module_name == "app"
310
+ assert "app" in bundle.client_functions
311
+
312
+ # Verify the import alias was processed by Vite
313
+ # Vite should have resolved the asset import
314
+ # The bundle should contain the processed asset URL
315
+ assert "burgerImage" in bundle.code
316
+
317
+ # Verify bundle was written to output directory
318
+ bundle_files = list(output_dir.glob("client.*.js"))
319
+ assert len(bundle_files) > 0, "Expected at least one bundle file"
320
+
321
+ # Cleanup
322
+ builder.cleanup_temp_dir()