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
@@ -0,0 +1,133 @@
1
+ """Test module for spawn operator in both standard and reverse order."""
2
+
3
+ # Server-side node and walker definitions
4
+ node TestNode {
5
+ has value: int = 0;
6
+ }
7
+
8
+ walker test_walker {
9
+ has message: str = "Hello from walker";
10
+
11
+ can execute with `root entry {
12
+ report {"result": self.message} ;
13
+ }
14
+ }
15
+
16
+ walker parameterized_walker {
17
+ has value: int;
18
+
19
+ can execute with `root entry {
20
+ report {"computed": self.value * 2} ;
21
+ }
22
+ }
23
+
24
+ walker positional_walker {
25
+ has label: str;
26
+ has count: int;
27
+ has metadata: dict = {};
28
+
29
+ can execute with `root entry {
30
+ report {
31
+ "label": self.label,
32
+ "count": self.count,
33
+ "meta": self.metadata
34
+ } ;
35
+ }
36
+ }
37
+
38
+ # Client-side code testing both spawn orderings
39
+ cl import from react {
40
+ useState,
41
+ useEffect
42
+ }
43
+
44
+ cl {
45
+ def app() -> any {
46
+ let [standardResult, setStandardResult] = useState(None);
47
+ let [standardComputed, setStandardComputed] = useState(None);
48
+ let [reverseResult, setReverseResult] = useState(None);
49
+ let [uuidResult, setUuidResult] = useState(None);
50
+ let [reverseUuidResult, setReverseUuidResult] = useState(None);
51
+ let [positionalResult, setPositionalResult] = useState(None);
52
+ let [spreadResult, setSpreadResult] = useState(None);
53
+
54
+ async def loadData() -> None {
55
+ # Test standard spawn order: node spawn walker()
56
+ data1 = root spawn test_walker();
57
+ setStandardResult(data1);
58
+
59
+ data2 = root spawn parameterized_walker(value=42);
60
+ setStandardComputed(data2);
61
+
62
+ # Test reverse spawn order: walker() spawn node
63
+ data3 = test_walker(message="Reverse spawn!") spawn root;
64
+ setReverseResult(data3);
65
+
66
+ # Test spawn with UUID string: uuid_string spawn walker()
67
+ node_id = "550e8400-e29b-41d4-a716-446655440000";
68
+ data4 = node_id spawn test_walker();
69
+ setUuidResult(data4);
70
+
71
+ # Test reverse spawn with UUID string: walker() spawn uuid_string
72
+ another_node_id = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
73
+ data5 = parameterized_walker(value=100) spawn another_node_id;
74
+ setReverseUuidResult(data5);
75
+
76
+ # Test positional walker arguments inferred from has fields
77
+ data6 = node_id spawn positional_walker("Node positional", 2);
78
+ setPositionalResult(data6);
79
+
80
+ # Test **kwargs via spread when walker is on left-hand side
81
+ extra_fields = {"metadata": {"source": "client-side"}};
82
+ data7 = positional_walker("Spread order", 5, **extra_fields) spawn root;
83
+ setSpreadResult(data7);
84
+ }
85
+
86
+ useEffect(lambda -> None{ loadData();} , []);
87
+
88
+ return <div>
89
+ <h1>
90
+ Spawn Operator Test
91
+ </h1>
92
+ <h2>
93
+ Standard Order (node spawn walker)
94
+ </h2>
95
+ <div>
96
+ Result: {JSON.stringify(standardResult)}
97
+ </div>
98
+ <div>
99
+ Computed: {JSON.stringify(standardComputed)}
100
+ </div>
101
+ <h2>
102
+ Reverse Order (walker spawn node)
103
+ </h2>
104
+ <div>
105
+ Result: {JSON.stringify(reverseResult)}
106
+ </div>
107
+ <h2>
108
+ UUID Spawn (uuid spawn walker)
109
+ </h2>
110
+ <div>
111
+ Result: {JSON.stringify(uuidResult)}
112
+ </div>
113
+ <h2>
114
+ Reverse UUID Spawn (walker spawn uuid)
115
+ </h2>
116
+ <div>
117
+ Result: {JSON.stringify(reverseUuidResult)}
118
+ </div>
119
+ <h2>
120
+ Positional Walker Arguments
121
+ </h2>
122
+ <div>
123
+ Result: {JSON.stringify(positionalResult)}
124
+ </div>
125
+ <h2>
126
+ Spread Walker Arguments
127
+ </h2>
128
+ <div>
129
+ Result: {JSON.stringify(spreadResult)}
130
+ </div>
131
+ </div>;
132
+ }
133
+ }
@@ -11,7 +11,7 @@ cl def FragmentTest() {
11
11
  cl def SpreadPropsTest() {
12
12
  # Test spread props
13
13
  unwrapped = {"id": "my-div", "class": "container", "data-role": "main"};
14
-
14
+
15
15
  return <div {...unwrapped}>
16
16
  <span>{"Spread props work!"}</span>
17
17
  </div>;
@@ -20,7 +20,7 @@ cl def SpreadPropsTest() {
20
20
  cl def MixedTest() {
21
21
  # Test mixing spread props with regular props
22
22
  baseStyle = {"id": "base-id", "color": "blue"};
23
-
23
+
24
24
  return <>
25
25
  <div {...baseStyle} class="override">
26
26
  {"Mixed test"}
@@ -42,3 +42,12 @@ cl def NestedFragments() {
42
42
  </div>;
43
43
  }
44
44
 
45
+ cl def app() {
46
+ return <div>
47
+ <FragmentTest />
48
+ <SpreadPropsTest />
49
+ <MixedTest />
50
+ <NestedFragments />
51
+ </div>;
52
+ }
53
+
@@ -0,0 +1,339 @@
1
+ """Tests for asset-serving and css-styling examples."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ import json
7
+ import shutil
8
+ import tempfile
9
+ import subprocess
10
+
11
+ from jaclang.runtimelib.machine import JacMachine as Jac
12
+ from jaclang.utils.test import TestCase
13
+ from jac_client.plugin.vite_client_bundle import ViteClientBundleBuilder
14
+
15
+
16
+ class AssetServingExampleTests(TestCase):
17
+ """Test asset-serving examples."""
18
+
19
+ def setUp(self) -> None:
20
+ Jac.reset_machine()
21
+ return super().setUp()
22
+
23
+ def tearDown(self) -> None:
24
+ Jac.reset_machine()
25
+ return super().tearDown()
26
+
27
+ def _create_test_project_with_vite(
28
+ self, temp_path: Path, include_assets: bool = False
29
+ ) -> tuple[Path, Path]:
30
+ """Create a minimal test project with Vite installed.
31
+
32
+ Args:
33
+ temp_path: Path to the temporary directory
34
+ include_assets: If True, includes asset handling setup
35
+ """
36
+ # Create package.json with base dependencies
37
+ package_data = {
38
+ "name": "test-client",
39
+ "version": "0.0.1",
40
+ "type": "module",
41
+ "scripts": {
42
+ "build": "npm run compile && vite build",
43
+ "dev": "vite dev",
44
+ "preview": "vite preview",
45
+ "compile": 'babel src --out-dir build --extensions ".jsx,.js" --out-file-extension .js',
46
+ },
47
+ "dependencies": {
48
+ "react": "^19.2.0",
49
+ "react-dom": "^19.2.0",
50
+ "react-router-dom": "^7.3.0",
51
+ },
52
+ "devDependencies": {
53
+ "vite": "^6.4.1",
54
+ "@babel/cli": "^7.28.3",
55
+ "@babel/core": "^7.28.5",
56
+ "@babel/preset-env": "^7.28.5",
57
+ "@babel/preset-react": "^7.28.5",
58
+ },
59
+ }
60
+
61
+ package_json = temp_path / "package.json"
62
+ with package_json.open("w", encoding="utf-8") as f:
63
+ json.dump(package_data, f, indent=2)
64
+
65
+ # Create .babelrc file
66
+ babelrc = temp_path / ".babelrc"
67
+ babelrc.write_text(
68
+ """{
69
+ "presets": [[
70
+ "@babel/preset-env",
71
+ {
72
+ "modules": false
73
+ }
74
+ ], "@babel/preset-react"]
75
+ }
76
+ """,
77
+ encoding="utf-8",
78
+ )
79
+
80
+ # Create vite.config.js file
81
+ vite_config = temp_path / "vite.config.js"
82
+ if include_assets:
83
+ vite_config.write_text(
84
+ """import { defineConfig } from "vite";
85
+ import path from "path";
86
+ import { fileURLToPath } from "url";
87
+
88
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
89
+
90
+ export default defineConfig({
91
+ root: ".",
92
+ build: {
93
+ rollupOptions: {
94
+ input: "build/main.js",
95
+ output: {
96
+ entryFileNames: "client.[hash].js",
97
+ assetFileNames: "[name].[ext]",
98
+ },
99
+ },
100
+ outDir: "dist",
101
+ emptyOutDir: true,
102
+ minify: false,
103
+ },
104
+ publicDir: false,
105
+ resolve: {
106
+ alias: {
107
+ "@jac-client/utils": path.resolve(__dirname, "src/client_runtime.js"),
108
+ "@jac-client/assets": path.resolve(__dirname, "src/assets"),
109
+ },
110
+ },
111
+ });
112
+ """,
113
+ encoding="utf-8",
114
+ )
115
+ else:
116
+ vite_config.write_text(
117
+ """import { defineConfig } from "vite";
118
+ import path from "path";
119
+ import { fileURLToPath } from "url";
120
+
121
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
122
+
123
+ export default defineConfig({
124
+ root: ".",
125
+ build: {
126
+ rollupOptions: {
127
+ input: "build/main.js",
128
+ output: {
129
+ entryFileNames: "client.[hash].js",
130
+ assetFileNames: "[name].[ext]",
131
+ },
132
+ },
133
+ outDir: "dist",
134
+ emptyOutDir: true,
135
+ minify: false,
136
+ },
137
+ publicDir: false,
138
+ resolve: {
139
+ alias: {
140
+ "@jac-client/utils": path.resolve(__dirname, "src/client_runtime.js"),
141
+ },
142
+ },
143
+ });
144
+ """,
145
+ encoding="utf-8",
146
+ )
147
+
148
+ # Install dependencies
149
+ result = subprocess.run(
150
+ ["npm", "install"],
151
+ cwd=temp_path,
152
+ check=False,
153
+ capture_output=True,
154
+ text=True,
155
+ )
156
+ if result.returncode != 0:
157
+ error_msg = f"npm install failed with exit code {result.returncode}\n"
158
+ error_msg += f"stdout: {result.stdout}\n"
159
+ error_msg += f"stderr: {result.stderr}\n"
160
+ raise RuntimeError(error_msg)
161
+
162
+ # Create output directory
163
+ output_dir = temp_path / "dist"
164
+ output_dir.mkdir(parents=True, exist_ok=True)
165
+
166
+ src_dir = temp_path / "src"
167
+ src_dir.mkdir(parents=True, exist_ok=True)
168
+
169
+ build_dir = temp_path / "build"
170
+ build_dir.mkdir(parents=True, exist_ok=True)
171
+
172
+ return package_json, output_dir
173
+
174
+ def test_image_asset_example(self) -> None:
175
+ """Test image-asset example with static asset paths."""
176
+ with tempfile.TemporaryDirectory() as temp_dir:
177
+ temp_path = Path(temp_dir)
178
+
179
+ package_json, output_dir = self._create_test_project_with_vite(
180
+ temp_path, include_assets=True
181
+ )
182
+ runtime_path = (
183
+ Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
184
+ )
185
+
186
+ # Initialize the Vite builder
187
+ builder = ViteClientBundleBuilder(
188
+ runtime_path=runtime_path,
189
+ vite_package_json=package_json,
190
+ vite_output_dir=output_dir,
191
+ vite_minify=False,
192
+ )
193
+
194
+ # Import the image-asset example
195
+ examples_dir = (
196
+ Path(__file__).parent.parent
197
+ / "examples"
198
+ / "asset-serving"
199
+ / "image-asset"
200
+ )
201
+ (module,) = Jac.jac_import("app", str(examples_dir))
202
+
203
+ # Build the bundle
204
+ bundle = builder.build(module, force=True)
205
+
206
+ # Verify bundle structure
207
+ self.assertIsNotNone(bundle)
208
+ self.assertEqual(bundle.module_name, "app")
209
+ self.assertIn("app", bundle.client_functions)
210
+
211
+ # Verify image path is in the bundle
212
+ self.assertIn("/static/assets/burger.png", bundle.code)
213
+
214
+ # Verify bundle was written to output directory
215
+ bundle_files = list(output_dir.glob("client.*.js"))
216
+ self.assertGreater(
217
+ len(bundle_files), 0, "Expected at least one bundle file"
218
+ )
219
+
220
+ # Cleanup
221
+ builder.cleanup_temp_dir()
222
+
223
+ def test_css_with_image_example(self) -> None:
224
+ """Test css-with-image example with CSS and image assets."""
225
+ with tempfile.TemporaryDirectory() as temp_dir:
226
+ temp_path = Path(temp_dir)
227
+
228
+ package_json, output_dir = self._create_test_project_with_vite(
229
+ temp_path, include_assets=True
230
+ )
231
+ runtime_path = (
232
+ Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
233
+ )
234
+
235
+ # Initialize the Vite builder
236
+ builder = ViteClientBundleBuilder(
237
+ runtime_path=runtime_path,
238
+ vite_package_json=package_json,
239
+ vite_output_dir=output_dir,
240
+ vite_minify=False,
241
+ )
242
+
243
+ # Import the css-with-image example
244
+ examples_dir = (
245
+ Path(__file__).parent.parent
246
+ / "examples"
247
+ / "asset-serving"
248
+ / "css-with-image"
249
+ )
250
+ (module,) = Jac.jac_import("app", str(examples_dir))
251
+
252
+ # Build the bundle
253
+ bundle = builder.build(module, force=True)
254
+
255
+ # Verify bundle structure
256
+ self.assertIsNotNone(bundle)
257
+ self.assertEqual(bundle.module_name, "app")
258
+ self.assertIn("app", bundle.client_functions)
259
+
260
+ # Verify CSS import is present (CSS should be extracted to separate file)
261
+ # The bundle should reference the CSS file
262
+ self.assertIn("import", bundle.code.lower())
263
+
264
+ # Verify image paths are in the bundle
265
+ self.assertIn("/static/assets/burger.png", bundle.code)
266
+
267
+ # Verify CSS file was extracted
268
+ css_files = list(output_dir.glob("*.css"))
269
+ self.assertGreater(len(css_files), 0, "Expected at least one CSS file")
270
+
271
+ # Verify bundle was written to output directory
272
+ bundle_files = list(output_dir.glob("client.*.js"))
273
+ self.assertGreater(
274
+ len(bundle_files), 0, "Expected at least one bundle file"
275
+ )
276
+
277
+ # Cleanup
278
+ builder.cleanup_temp_dir()
279
+
280
+ def test_import_alias_example(self) -> None:
281
+ """Test import-alias example with @jac-client/assets alias."""
282
+ with tempfile.TemporaryDirectory() as temp_dir:
283
+ temp_path = Path(temp_dir)
284
+ package_json, output_dir = self._create_test_project_with_vite(
285
+ temp_path, include_assets=True
286
+ )
287
+ runtime_path = (
288
+ Path(__file__).parent.parent / "plugin" / "client_runtime.jac"
289
+ )
290
+
291
+ # Initialize the Vite builder
292
+ builder = ViteClientBundleBuilder(
293
+ runtime_path=runtime_path,
294
+ vite_package_json=package_json,
295
+ vite_output_dir=output_dir,
296
+ vite_minify=False,
297
+ )
298
+
299
+ # Import the import-alias example
300
+ examples_dir = (
301
+ Path(__file__).parent.parent
302
+ / "examples"
303
+ / "asset-serving"
304
+ / "import-alias"
305
+ )
306
+ (module,) = Jac.jac_import("app", str(examples_dir))
307
+
308
+ # Copy assets from example directory to temp project's src/assets/
309
+ # This is needed because @jac-client/assets alias points to src/assets
310
+ example_assets_dir = examples_dir / "assets"
311
+ temp_assets_dir = temp_path / "src" / "assets"
312
+ if example_assets_dir.exists():
313
+ temp_assets_dir.mkdir(parents=True, exist_ok=True)
314
+ # Copy all files from example assets to temp assets
315
+ for asset_file in example_assets_dir.iterdir():
316
+ if asset_file.is_file():
317
+ shutil.copy2(asset_file, temp_assets_dir / asset_file.name)
318
+
319
+ # Build the bundle
320
+ bundle = builder.build(module, force=True)
321
+
322
+ # Verify bundle structure
323
+ self.assertIsNotNone(bundle)
324
+ self.assertEqual(bundle.module_name, "app")
325
+ self.assertIn("app", bundle.client_functions)
326
+
327
+ # Verify the import alias was processed by Vite
328
+ # Vite should have resolved the asset import
329
+ # The bundle should contain the processed asset URL
330
+ self.assertIn("burgerImage", bundle.code)
331
+
332
+ # Verify bundle was written to output directory
333
+ bundle_files = list(output_dir.glob("client.*.js"))
334
+ self.assertGreater(
335
+ len(bundle_files), 0, "Expected at least one bundle file"
336
+ )
337
+
338
+ # Cleanup
339
+ builder.cleanup_temp_dir()