jac-client 0.2.2__py3-none-any.whl → 0.2.4__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 (176) hide show
  1. jac_client/examples/all-in-one/assets/workers/worker.py +5 -0
  2. jac_client/tests/conftest.py +281 -0
  3. jac_client/tests/test_cli.py +755 -0
  4. jac_client/tests/test_it.py +347 -67
  5. {jac_client-0.2.2.dist-info → jac_client-0.2.4.dist-info}/METADATA +30 -24
  6. jac_client-0.2.4.dist-info/RECORD +10 -0
  7. {jac_client-0.2.2.dist-info → jac_client-0.2.4.dist-info}/WHEEL +2 -1
  8. jac_client-0.2.4.dist-info/entry_points.txt +4 -0
  9. jac_client-0.2.4.dist-info/top_level.txt +1 -0
  10. jac_client/docs/README.md +0 -689
  11. jac_client/docs/advanced-state.md +0 -1265
  12. jac_client/docs/asset-serving/intro.md +0 -209
  13. jac_client/docs/assets/pipe_line-v2.svg +0 -32
  14. jac_client/docs/assets/pipe_line.png +0 -0
  15. jac_client/docs/file-system/app.jac.md +0 -121
  16. jac_client/docs/file-system/backend-frontend.md +0 -217
  17. jac_client/docs/file-system/intro.md +0 -72
  18. jac_client/docs/file-system/nested-imports.md +0 -348
  19. jac_client/docs/guide-example/intro.md +0 -115
  20. jac_client/docs/guide-example/step-01-setup.md +0 -270
  21. jac_client/docs/guide-example/step-02-components.md +0 -416
  22. jac_client/docs/guide-example/step-03-styling.md +0 -478
  23. jac_client/docs/guide-example/step-04-todo-ui.md +0 -477
  24. jac_client/docs/guide-example/step-05-local-state.md +0 -530
  25. jac_client/docs/guide-example/step-06-events.md +0 -749
  26. jac_client/docs/guide-example/step-07-effects.md +0 -468
  27. jac_client/docs/guide-example/step-08-walkers.md +0 -534
  28. jac_client/docs/guide-example/step-09-authentication.md +0 -586
  29. jac_client/docs/guide-example/step-10-routing.md +0 -539
  30. jac_client/docs/guide-example/step-11-final.md +0 -963
  31. jac_client/docs/imports.md +0 -1141
  32. jac_client/docs/lifecycle-hooks.md +0 -773
  33. jac_client/docs/routing.md +0 -659
  34. jac_client/docs/styling/intro.md +0 -249
  35. jac_client/docs/styling/js-styling.md +0 -367
  36. jac_client/docs/styling/material-ui.md +0 -341
  37. jac_client/docs/styling/pure-css.md +0 -299
  38. jac_client/docs/styling/sass.md +0 -403
  39. jac_client/docs/styling/styled-components.md +0 -395
  40. jac_client/docs/styling/tailwind.md +0 -298
  41. jac_client/examples/all-in-one/.babelrc +0 -9
  42. jac_client/examples/all-in-one/README.md +0 -16
  43. jac_client/examples/all-in-one/app.jac +0 -426
  44. jac_client/examples/all-in-one/assets/burger.png +0 -0
  45. jac_client/examples/all-in-one/button.jac +0 -7
  46. jac_client/examples/all-in-one/components/button.jac +0 -7
  47. jac_client/examples/all-in-one/package.json +0 -29
  48. jac_client/examples/all-in-one/styles.css +0 -26
  49. jac_client/examples/all-in-one/vite.config.js +0 -28
  50. jac_client/examples/asset-serving/css-with-image/.babelrc +0 -9
  51. jac_client/examples/asset-serving/css-with-image/README.md +0 -91
  52. jac_client/examples/asset-serving/css-with-image/app.jac +0 -88
  53. jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
  54. jac_client/examples/asset-serving/css-with-image/package.json +0 -28
  55. jac_client/examples/asset-serving/css-with-image/styles.css +0 -26
  56. jac_client/examples/asset-serving/css-with-image/vite.config.js +0 -28
  57. jac_client/examples/asset-serving/image-asset/.babelrc +0 -9
  58. jac_client/examples/asset-serving/image-asset/README.md +0 -119
  59. jac_client/examples/asset-serving/image-asset/app.jac +0 -55
  60. jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
  61. jac_client/examples/asset-serving/image-asset/package.json +0 -28
  62. jac_client/examples/asset-serving/image-asset/styles.css +0 -26
  63. jac_client/examples/asset-serving/image-asset/vite.config.js +0 -28
  64. jac_client/examples/asset-serving/import-alias/.babelrc +0 -9
  65. jac_client/examples/asset-serving/import-alias/README.md +0 -83
  66. jac_client/examples/asset-serving/import-alias/app.jac +0 -111
  67. jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
  68. jac_client/examples/asset-serving/import-alias/package.json +0 -28
  69. jac_client/examples/asset-serving/import-alias/vite.config.js +0 -28
  70. jac_client/examples/basic/.babelrc +0 -9
  71. jac_client/examples/basic/README.md +0 -16
  72. jac_client/examples/basic/app.jac +0 -21
  73. jac_client/examples/basic/package.json +0 -27
  74. jac_client/examples/basic/vite.config.js +0 -27
  75. jac_client/examples/basic-auth/.babelrc +0 -9
  76. jac_client/examples/basic-auth/README.md +0 -16
  77. jac_client/examples/basic-auth/app.jac +0 -308
  78. jac_client/examples/basic-auth/package.json +0 -27
  79. jac_client/examples/basic-auth/vite.config.js +0 -27
  80. jac_client/examples/basic-auth-with-router/.babelrc +0 -9
  81. jac_client/examples/basic-auth-with-router/README.md +0 -60
  82. jac_client/examples/basic-auth-with-router/app.jac +0 -464
  83. jac_client/examples/basic-auth-with-router/package.json +0 -28
  84. jac_client/examples/basic-auth-with-router/vite.config.js +0 -27
  85. jac_client/examples/basic-full-stack/.babelrc +0 -9
  86. jac_client/examples/basic-full-stack/README.md +0 -18
  87. jac_client/examples/basic-full-stack/app.jac +0 -320
  88. jac_client/examples/basic-full-stack/package.json +0 -28
  89. jac_client/examples/basic-full-stack/vite.config.js +0 -27
  90. jac_client/examples/css-styling/js-styling/.babelrc +0 -9
  91. jac_client/examples/css-styling/js-styling/README.md +0 -183
  92. jac_client/examples/css-styling/js-styling/app.jac +0 -84
  93. jac_client/examples/css-styling/js-styling/package.json +0 -28
  94. jac_client/examples/css-styling/js-styling/styles.js +0 -100
  95. jac_client/examples/css-styling/js-styling/vite.config.js +0 -27
  96. jac_client/examples/css-styling/material-ui/.babelrc +0 -9
  97. jac_client/examples/css-styling/material-ui/README.md +0 -16
  98. jac_client/examples/css-styling/material-ui/app.jac +0 -122
  99. jac_client/examples/css-styling/material-ui/package.json +0 -32
  100. jac_client/examples/css-styling/material-ui/vite.config.js +0 -27
  101. jac_client/examples/css-styling/pure-css/.babelrc +0 -9
  102. jac_client/examples/css-styling/pure-css/README.md +0 -16
  103. jac_client/examples/css-styling/pure-css/app.jac +0 -64
  104. jac_client/examples/css-styling/pure-css/package.json +0 -28
  105. jac_client/examples/css-styling/pure-css/styles.css +0 -111
  106. jac_client/examples/css-styling/pure-css/vite.config.js +0 -27
  107. jac_client/examples/css-styling/sass-example/.babelrc +0 -9
  108. jac_client/examples/css-styling/sass-example/README.md +0 -16
  109. jac_client/examples/css-styling/sass-example/app.jac +0 -64
  110. jac_client/examples/css-styling/sass-example/package.json +0 -29
  111. jac_client/examples/css-styling/sass-example/styles.scss +0 -153
  112. jac_client/examples/css-styling/sass-example/vite.config.js +0 -27
  113. jac_client/examples/css-styling/styled-components/.babelrc +0 -9
  114. jac_client/examples/css-styling/styled-components/README.md +0 -16
  115. jac_client/examples/css-styling/styled-components/app.jac +0 -71
  116. jac_client/examples/css-styling/styled-components/package.json +0 -29
  117. jac_client/examples/css-styling/styled-components/styled.js +0 -90
  118. jac_client/examples/css-styling/styled-components/vite.config.js +0 -27
  119. jac_client/examples/css-styling/tailwind-example/.babelrc +0 -9
  120. jac_client/examples/css-styling/tailwind-example/README.md +0 -16
  121. jac_client/examples/css-styling/tailwind-example/app.jac +0 -63
  122. jac_client/examples/css-styling/tailwind-example/global.css +0 -1
  123. jac_client/examples/css-styling/tailwind-example/package.json +0 -30
  124. jac_client/examples/css-styling/tailwind-example/vite.config.js +0 -29
  125. jac_client/examples/full-stack-with-auth/.babelrc +0 -9
  126. jac_client/examples/full-stack-with-auth/README.md +0 -16
  127. jac_client/examples/full-stack-with-auth/app.jac +0 -722
  128. jac_client/examples/full-stack-with-auth/package.json +0 -28
  129. jac_client/examples/full-stack-with-auth/vite.config.js +0 -29
  130. jac_client/examples/little-x/app.jac +0 -724
  131. jac_client/examples/little-x/package.json +0 -23
  132. jac_client/examples/little-x/submit-button.jac +0 -8
  133. jac_client/examples/nested-folders/nested-advance/.babelrc +0 -9
  134. jac_client/examples/nested-folders/nested-advance/ButtonRoot.jac +0 -11
  135. jac_client/examples/nested-folders/nested-advance/README.md +0 -77
  136. jac_client/examples/nested-folders/nested-advance/app.jac +0 -35
  137. jac_client/examples/nested-folders/nested-advance/level1/ButtonSecondL.jac +0 -19
  138. jac_client/examples/nested-folders/nested-advance/level1/Card.jac +0 -43
  139. jac_client/examples/nested-folders/nested-advance/level1/level2/ButtonThirdL.jac +0 -25
  140. jac_client/examples/nested-folders/nested-advance/package.json +0 -29
  141. jac_client/examples/nested-folders/nested-advance/vite.config.js +0 -28
  142. jac_client/examples/nested-folders/nested-basic/.babelrc +0 -9
  143. jac_client/examples/nested-folders/nested-basic/README.md +0 -183
  144. jac_client/examples/nested-folders/nested-basic/app.jac +0 -13
  145. jac_client/examples/nested-folders/nested-basic/app.js +0 -7
  146. jac_client/examples/nested-folders/nested-basic/button.jac +0 -7
  147. jac_client/examples/nested-folders/nested-basic/components/button.jac +0 -7
  148. jac_client/examples/nested-folders/nested-basic/package.json +0 -28
  149. jac_client/examples/nested-folders/nested-basic/vite.config.js +0 -27
  150. jac_client/examples/with-router/.babelrc +0 -9
  151. jac_client/examples/with-router/README.md +0 -17
  152. jac_client/examples/with-router/app.jac +0 -323
  153. jac_client/examples/with-router/package.json +0 -28
  154. jac_client/examples/with-router/vite.config.js +0 -27
  155. jac_client/plugin/cli.py +0 -244
  156. jac_client/plugin/client.py +0 -152
  157. jac_client/plugin/client_runtime.jac +0 -234
  158. jac_client/plugin/vite_client_bundle.py +0 -503
  159. jac_client/tests/fixtures/basic-app/app.jac +0 -23
  160. jac_client/tests/fixtures/cl_file/app.cl.jac +0 -48
  161. jac_client/tests/fixtures/cl_file/app.jac +0 -15
  162. jac_client/tests/fixtures/client_app_with_antd/app.jac +0 -34
  163. jac_client/tests/fixtures/js_import/app.jac +0 -34
  164. jac_client/tests/fixtures/js_import/utils.js +0 -21
  165. jac_client/tests/fixtures/package-lock.json +0 -329
  166. jac_client/tests/fixtures/package.json +0 -11
  167. jac_client/tests/fixtures/relative_import/app.jac +0 -11
  168. jac_client/tests/fixtures/relative_import/button.jac +0 -7
  169. jac_client/tests/fixtures/spawn_test/app.jac +0 -129
  170. jac_client/tests/fixtures/test_fragments_spread/app.jac +0 -67
  171. jac_client/tests/test_asset_examples.py +0 -322
  172. jac_client/tests/test_cl.py +0 -530
  173. jac_client/tests/test_create_jac_app.py +0 -131
  174. jac_client/tests/test_nested_file.py +0 -374
  175. jac_client-0.2.2.dist-info/RECORD +0 -171
  176. jac_client-0.2.2.dist-info/entry_points.txt +0 -4
@@ -0,0 +1,755 @@
1
+ """Test create-jac-app command."""
2
+
3
+ import os
4
+ import tempfile
5
+ import tomllib
6
+ from subprocess import PIPE, Popen, run
7
+
8
+
9
+ def test_create_jac_app() -> None:
10
+ """Test jac create --cl command."""
11
+ test_project_name = "test-jac-app"
12
+
13
+ # Create a temporary directory for testing
14
+ with tempfile.TemporaryDirectory() as temp_dir:
15
+ original_cwd = os.getcwd()
16
+ try:
17
+ # Change to temp directory
18
+ os.chdir(temp_dir)
19
+
20
+ # Run jac create --cl command
21
+ process = Popen(
22
+ ["jac", "create", "--cl", test_project_name],
23
+ stdin=PIPE,
24
+ stdout=PIPE,
25
+ stderr=PIPE,
26
+ text=True,
27
+ )
28
+ stdout, stderr = process.communicate()
29
+ result_code = process.returncode
30
+
31
+ # Check that command succeeded
32
+ assert result_code == 0
33
+ assert f"Project '{test_project_name}' created successfully!" in stdout
34
+
35
+ # Verify project directory was created
36
+ project_path = os.path.join(temp_dir, test_project_name)
37
+ assert os.path.exists(project_path)
38
+ assert os.path.isdir(project_path)
39
+
40
+ # Verify src/app.jac file was created
41
+ app_jac_path = os.path.join(project_path, "src", "app.jac")
42
+ assert os.path.exists(app_jac_path)
43
+
44
+ with open(app_jac_path) as f:
45
+ app_jac_content = f.read()
46
+
47
+ assert "def:pub app()" in app_jac_content
48
+
49
+ # Verify README.md was created
50
+ readme_path = os.path.join(project_path, "README.md")
51
+ assert os.path.exists(readme_path)
52
+
53
+ with open(readme_path) as f:
54
+ readme_content = f.read()
55
+
56
+ assert f"# {test_project_name}" in readme_content
57
+ assert "jac serve src/app.jac" in readme_content
58
+
59
+ # Verify jac.toml was created
60
+ jac_toml_path = os.path.join(project_path, "jac.toml")
61
+ assert os.path.exists(jac_toml_path)
62
+
63
+ with open(jac_toml_path, "rb") as f:
64
+ config_data = tomllib.load(f)
65
+
66
+ assert config_data["project"]["name"] == test_project_name
67
+
68
+ # Verify .gitignore was created with correct content
69
+ gitignore_path = os.path.join(project_path, ".gitignore")
70
+ assert os.path.exists(gitignore_path)
71
+
72
+ with open(gitignore_path) as f:
73
+ gitignore_content = f.read()
74
+
75
+ assert "node_modules" in gitignore_content
76
+
77
+ # Verify src/components directory exists
78
+ components_dir = os.path.join(project_path, "src", "components")
79
+ assert os.path.exists(components_dir)
80
+
81
+ # Verify default packages installation (package.json should be generated)
82
+ package_json_path = os.path.join(
83
+ project_path, ".client-build", ".jac-client.configs", "package.json"
84
+ )
85
+ # Note: packages may or may not be installed depending on npm availability
86
+ # but package.json should be generated with default packages
87
+ if os.path.exists(package_json_path):
88
+ import json
89
+
90
+ with open(package_json_path) as f:
91
+ package_data = json.load(f)
92
+
93
+ # Verify default dependencies are in package.json
94
+ assert "react" in package_data.get("dependencies", {})
95
+ assert "react-dom" in package_data.get("dependencies", {})
96
+ assert "vite" in package_data.get("devDependencies", {})
97
+
98
+ finally:
99
+ # Return to original directory
100
+ os.chdir(original_cwd)
101
+
102
+
103
+ def test_create_jac_app_invalid_name() -> None:
104
+ """Test jac create --cl command with invalid project name."""
105
+ with tempfile.TemporaryDirectory() as temp_dir:
106
+ original_cwd = os.getcwd()
107
+ try:
108
+ os.chdir(temp_dir)
109
+
110
+ # Test with invalid name containing spaces
111
+ result = run(
112
+ ["jac", "create", "--cl", "invalid name with spaces"],
113
+ capture_output=True,
114
+ text=True,
115
+ )
116
+
117
+ # Should fail with non-zero exit code
118
+ assert result.returncode != 0
119
+ assert (
120
+ "Project name must contain only letters, numbers, hyphens, and underscores"
121
+ in result.stderr
122
+ )
123
+
124
+ finally:
125
+ os.chdir(original_cwd)
126
+
127
+
128
+ def test_create_jac_app_existing_directory() -> None:
129
+ """Test jac create --cl command when directory already exists."""
130
+ test_project_name = "existing-test-app"
131
+
132
+ with tempfile.TemporaryDirectory() as temp_dir:
133
+ original_cwd = os.getcwd()
134
+ try:
135
+ os.chdir(temp_dir)
136
+
137
+ # Create the directory first
138
+ os.makedirs(test_project_name)
139
+
140
+ # Try to create app with same name
141
+ process = Popen(
142
+ ["jac", "create", "--cl", test_project_name],
143
+ stdin=PIPE,
144
+ stdout=PIPE,
145
+ stderr=PIPE,
146
+ text=True,
147
+ )
148
+ stdout, stderr = process.communicate()
149
+ result_code = process.returncode
150
+
151
+ # Should fail with non-zero exit code
152
+ assert result_code != 0
153
+ assert f"Directory '{test_project_name}' already exists" in stderr
154
+
155
+ finally:
156
+ os.chdir(original_cwd)
157
+
158
+
159
+ def test_create_jac_app_with_typescript() -> None:
160
+ """Test jac create --cl command with TypeScript support (enabled by default)."""
161
+ test_project_name = "test-jac-app-ts"
162
+
163
+ # Create a temporary directory for testing
164
+ with tempfile.TemporaryDirectory() as temp_dir:
165
+ original_cwd = os.getcwd()
166
+ try:
167
+ # Change to temp directory
168
+ os.chdir(temp_dir)
169
+
170
+ # Run jac create --cl command (TypeScript is enabled by default)
171
+ process = Popen(
172
+ ["jac", "create", "--cl", test_project_name],
173
+ stdin=PIPE,
174
+ stdout=PIPE,
175
+ stderr=PIPE,
176
+ text=True,
177
+ )
178
+ stdout, stderr = process.communicate()
179
+ result_code = process.returncode
180
+
181
+ # Check that command succeeded
182
+ assert result_code == 0
183
+ assert f"Project '{test_project_name}' created successfully!" in stdout
184
+
185
+ # Verify project directory was created
186
+ project_path = os.path.join(temp_dir, test_project_name)
187
+ assert os.path.exists(project_path)
188
+ assert os.path.isdir(project_path)
189
+
190
+ # Verify jac.toml was created
191
+ jac_toml_path = os.path.join(project_path, "jac.toml")
192
+ assert os.path.exists(jac_toml_path)
193
+
194
+ with open(jac_toml_path, "rb") as f:
195
+ config_data = tomllib.load(f)
196
+
197
+ assert config_data["project"]["name"] == test_project_name
198
+
199
+ # Verify src/components directory and Button.tsx were created
200
+ components_dir = os.path.join(project_path, "src", "components")
201
+ assert os.path.exists(components_dir)
202
+ assert os.path.isdir(components_dir)
203
+
204
+ button_tsx_path = os.path.join(components_dir, "Button.tsx")
205
+ assert os.path.exists(button_tsx_path)
206
+
207
+ with open(button_tsx_path) as f:
208
+ button_content = f.read()
209
+
210
+ assert "interface ButtonProps" in button_content
211
+ assert "export const Button" in button_content
212
+
213
+ # Verify src/app.jac includes TypeScript import
214
+ app_jac_path = os.path.join(project_path, "src", "app.jac")
215
+ assert os.path.exists(app_jac_path)
216
+
217
+ with open(app_jac_path) as f:
218
+ app_jac_content = f.read()
219
+
220
+ assert (
221
+ 'cl import from ".components/Button.tsx" { Button }' in app_jac_content
222
+ )
223
+ assert "<Button" in app_jac_content
224
+
225
+ # Verify README.md includes TypeScript information
226
+ readme_path = os.path.join(project_path, "README.md")
227
+ assert os.path.exists(readme_path)
228
+
229
+ with open(readme_path) as f:
230
+ readme_content = f.read()
231
+
232
+ assert "TypeScript Support" in readme_content
233
+ assert "components/Button.tsx" in readme_content
234
+
235
+ # Verify default packages installation (package.json should be generated)
236
+ package_json_path = os.path.join(
237
+ project_path, ".client-build", ".jac-client.configs", "package.json"
238
+ )
239
+ # Note: packages may or may not be installed depending on npm availability
240
+ # but package.json should be generated with default packages
241
+ if os.path.exists(package_json_path):
242
+ import json
243
+
244
+ with open(package_json_path) as f:
245
+ package_data = json.load(f)
246
+
247
+ # Verify default dependencies are in package.json
248
+ assert "react" in package_data.get("dependencies", {})
249
+ assert "react-dom" in package_data.get("dependencies", {})
250
+ assert "vite" in package_data.get("devDependencies", {})
251
+ assert "typescript" in package_data.get("devDependencies", {})
252
+
253
+ finally:
254
+ # Return to original directory
255
+ os.chdir(original_cwd)
256
+
257
+
258
+ def test_create_jac_app_with_skip_flag() -> None:
259
+ """Test jac create --cl --skip command skips package installation."""
260
+ test_project_name = "test-jac-app-skip"
261
+
262
+ # Create a temporary directory for testing
263
+ with tempfile.TemporaryDirectory() as temp_dir:
264
+ original_cwd = os.getcwd()
265
+ try:
266
+ # Change to temp directory
267
+ os.chdir(temp_dir)
268
+
269
+ # Run jac create --cl --skip command
270
+ process = Popen(
271
+ ["jac", "create", "--cl", "--skip", test_project_name],
272
+ stdin=PIPE,
273
+ stdout=PIPE,
274
+ stderr=PIPE,
275
+ text=True,
276
+ )
277
+ stdout, stderr = process.communicate()
278
+ result_code = process.returncode
279
+
280
+ # Check that command succeeded
281
+ assert result_code == 0
282
+ assert f"Project '{test_project_name}' created successfully!" in stdout
283
+
284
+ # Verify project directory was created
285
+ project_path = os.path.join(temp_dir, test_project_name)
286
+ assert os.path.exists(project_path)
287
+ assert os.path.isdir(project_path)
288
+
289
+ # Verify that "Installing default packages" message is NOT in output
290
+ assert "Installing default packages" not in stdout
291
+ assert "Default packages installed successfully" not in stdout
292
+
293
+ # Verify jac.toml was created
294
+ jac_toml_path = os.path.join(project_path, "jac.toml")
295
+ assert os.path.exists(jac_toml_path)
296
+
297
+ finally:
298
+ # Return to original directory
299
+ os.chdir(original_cwd)
300
+
301
+
302
+ def test_create_jac_app_installs_default_packages() -> None:
303
+ """Test jac create --cl command attempts to install default packages."""
304
+ test_project_name = "test-jac-app-install"
305
+
306
+ # Create a temporary directory for testing
307
+ with tempfile.TemporaryDirectory() as temp_dir:
308
+ original_cwd = os.getcwd()
309
+ try:
310
+ # Change to temp directory
311
+ os.chdir(temp_dir)
312
+
313
+ # Run jac create --cl command
314
+ process = Popen(
315
+ ["jac", "create", "--cl", test_project_name],
316
+ stdin=PIPE,
317
+ stdout=PIPE,
318
+ stderr=PIPE,
319
+ text=True,
320
+ )
321
+ stdout, stderr = process.communicate()
322
+ result_code = process.returncode
323
+
324
+ # Check that command succeeded
325
+ assert result_code == 0
326
+ assert f"Project '{test_project_name}' created successfully!" in stdout
327
+
328
+ # Verify project directory was created
329
+ project_path = os.path.join(temp_dir, test_project_name)
330
+ assert os.path.exists(project_path)
331
+
332
+ # Verify that installation was attempted (message should be in output)
333
+ assert "Installing default packages" in stdout
334
+
335
+ # Verify package.json was generated (even if npm install failed)
336
+ package_json_path = os.path.join(
337
+ project_path, ".client-build", ".jac-client.configs", "package.json"
338
+ )
339
+ # package.json should be generated with default packages
340
+ if os.path.exists(package_json_path):
341
+ import json
342
+
343
+ with open(package_json_path) as f:
344
+ package_data = json.load(f)
345
+
346
+ # Verify default dependencies are in package.json
347
+ deps = package_data.get("dependencies", {})
348
+ dev_deps = package_data.get("devDependencies", {})
349
+
350
+ assert "react" in deps
351
+ assert "react-dom" in deps
352
+ assert "react-router-dom" in deps
353
+ assert "vite" in dev_deps
354
+ assert "@babel/core" in dev_deps
355
+ assert "typescript" in dev_deps
356
+ assert "@types/react" in dev_deps
357
+
358
+ finally:
359
+ # Return to original directory
360
+ os.chdir(original_cwd)
361
+
362
+
363
+ def test_generate_client_config() -> None:
364
+ """Test that generate_client_config command no longer exists (use jac init instead)."""
365
+ with tempfile.TemporaryDirectory() as temp_dir:
366
+ original_cwd = os.getcwd()
367
+ try:
368
+ os.chdir(temp_dir)
369
+
370
+ # Run generate_client_config command - should not exist
371
+ result = run(
372
+ ["jac", "generate_client_config"],
373
+ capture_output=True,
374
+ text=True,
375
+ )
376
+
377
+ # Command should not exist anymore
378
+ assert result.returncode != 0
379
+
380
+ finally:
381
+ os.chdir(original_cwd)
382
+
383
+
384
+ def test_generate_client_config_existing_file() -> None:
385
+ """Test that generate_client_config command no longer exists."""
386
+ with tempfile.TemporaryDirectory() as temp_dir:
387
+ original_cwd = os.getcwd()
388
+ try:
389
+ os.chdir(temp_dir)
390
+
391
+ # Run generate_client_config command - should not exist
392
+ result = run(
393
+ ["jac", "generate_client_config"],
394
+ capture_output=True,
395
+ text=True,
396
+ )
397
+
398
+ # Command should not exist anymore
399
+ assert result.returncode != 0
400
+
401
+ finally:
402
+ os.chdir(original_cwd)
403
+
404
+
405
+ def _create_jac_toml(temp_dir: str, deps: str = "", dev_deps: str = "") -> str:
406
+ """Create a minimal jac.toml file for testing.
407
+
408
+ Note: These CLI tests run jac commands as subprocesses and include npm install.
409
+ For faster tests, consider using unit tests with mocked PackageInstaller.
410
+ """
411
+ deps_section = f"\n{deps}" if deps else ""
412
+ dev_deps_section = f"\n{dev_deps}" if dev_deps else ""
413
+
414
+ toml_content = f"""[project]
415
+ name = "test-project"
416
+ version = "1.0.0"
417
+ description = "Test project"
418
+ entry-point = "app.jac"
419
+
420
+ [dependencies.npm]{deps_section}
421
+
422
+ [dev-dependencies.npm]{dev_deps_section}
423
+ """
424
+ config_path = os.path.join(temp_dir, "jac.toml")
425
+ with open(config_path, "w") as f:
426
+ f.write(toml_content)
427
+ return config_path
428
+
429
+
430
+ def test_install_without_cl_flag() -> None:
431
+ """Test add command without --cl flag should fail when no jac.toml exists."""
432
+ with tempfile.TemporaryDirectory() as temp_dir:
433
+ original_cwd = os.getcwd()
434
+ try:
435
+ os.chdir(temp_dir)
436
+
437
+ # Run add command without --cl flag and without jac.toml
438
+ result = run(
439
+ ["jac", "add", "lodash"],
440
+ capture_output=True,
441
+ text=True,
442
+ )
443
+
444
+ # Should fail with non-zero exit code because no jac.toml
445
+ assert result.returncode != 0
446
+ assert "No jac.toml found" in result.stderr
447
+
448
+ finally:
449
+ os.chdir(original_cwd)
450
+
451
+
452
+ def test_install_all_packages() -> None:
453
+ """Test add --cl command installs all packages from jac.toml."""
454
+ with tempfile.TemporaryDirectory() as temp_dir:
455
+ original_cwd = os.getcwd()
456
+ try:
457
+ os.chdir(temp_dir)
458
+
459
+ # Create jac.toml with some dependencies
460
+ _create_jac_toml(temp_dir, deps='lodash = "^4.17.21"')
461
+
462
+ # Run add --cl command without package name
463
+ result = run(
464
+ ["jac", "add", "--cl"],
465
+ capture_output=True,
466
+ text=True,
467
+ )
468
+
469
+ # Should succeed
470
+ assert result.returncode == 0
471
+ assert "Installing all npm packages" in result.stdout
472
+ assert "Installed all npm packages successfully" in result.stdout
473
+
474
+ finally:
475
+ os.chdir(original_cwd)
476
+
477
+
478
+ def test_install_package_to_dependencies() -> None:
479
+ """Test add --cl command adds package to dependencies."""
480
+ with tempfile.TemporaryDirectory() as temp_dir:
481
+ original_cwd = os.getcwd()
482
+ try:
483
+ os.chdir(temp_dir)
484
+
485
+ # Create jac.toml
486
+ config_path = _create_jac_toml(temp_dir)
487
+
488
+ # Run add --cl command with package name
489
+ result = run(
490
+ ["jac", "add", "--cl", "lodash"],
491
+ capture_output=True,
492
+ text=True,
493
+ )
494
+
495
+ # Should succeed
496
+ assert result.returncode == 0
497
+ assert "Adding lodash (npm)" in result.stdout
498
+ assert "Added 1 package(s) to [dependencies.npm]" in result.stdout
499
+
500
+ # Verify package was added to jac.toml
501
+ with open(config_path, "rb") as f:
502
+ updated_config = tomllib.load(f)
503
+
504
+ assert "lodash" in updated_config["dependencies"]["npm"]
505
+
506
+ finally:
507
+ os.chdir(original_cwd)
508
+
509
+
510
+ def test_install_package_with_version() -> None:
511
+ """Test add --cl command with specific version."""
512
+ with tempfile.TemporaryDirectory() as temp_dir:
513
+ original_cwd = os.getcwd()
514
+ try:
515
+ os.chdir(temp_dir)
516
+
517
+ # Create jac.toml
518
+ config_path = _create_jac_toml(temp_dir)
519
+
520
+ # Run add --cl command with package and version
521
+ result = run(
522
+ ["jac", "add", "--cl", "lodash@^4.17.21"],
523
+ capture_output=True,
524
+ text=True,
525
+ )
526
+
527
+ # Should succeed
528
+ assert result.returncode == 0
529
+ assert "Adding lodash (npm)" in result.stdout
530
+ assert "Added 1 package(s) to [dependencies.npm]" in result.stdout
531
+
532
+ # Verify package was added with correct version
533
+ with open(config_path, "rb") as f:
534
+ updated_config = tomllib.load(f)
535
+
536
+ assert updated_config["dependencies"]["npm"]["lodash"] == "^4.17.21"
537
+
538
+ finally:
539
+ os.chdir(original_cwd)
540
+
541
+
542
+ def test_install_package_to_devdependencies() -> None:
543
+ """Test add --cl -d command adds package to dev-dependencies."""
544
+ with tempfile.TemporaryDirectory() as temp_dir:
545
+ original_cwd = os.getcwd()
546
+ try:
547
+ os.chdir(temp_dir)
548
+
549
+ # Create jac.toml
550
+ config_path = _create_jac_toml(temp_dir)
551
+
552
+ # Run add --cl -d command
553
+ run(
554
+ ["jac", "add", "--cl", "-d", "@types/react"],
555
+ capture_output=True,
556
+ text=True,
557
+ )
558
+
559
+ # Verify package was added to dev-dependencies in jac.toml
560
+ with open(config_path, "rb") as f:
561
+ updated_config = tomllib.load(f)
562
+
563
+ npm_deps = updated_config["dependencies"]["npm"]
564
+ assert "@types/react" in npm_deps.get("dev", {})
565
+ # Check it's not in regular deps (excluding the "dev" key)
566
+ regular_deps = {k: v for k, v in npm_deps.items() if k != "dev"}
567
+ assert "@types/react" not in regular_deps
568
+
569
+ finally:
570
+ os.chdir(original_cwd)
571
+
572
+
573
+ def test_install_without_config_json() -> None:
574
+ """Test add --cl command when jac.toml doesn't exist."""
575
+ with tempfile.TemporaryDirectory() as temp_dir:
576
+ original_cwd = os.getcwd()
577
+ try:
578
+ os.chdir(temp_dir)
579
+
580
+ # Run add --cl command without jac.toml
581
+ result = run(
582
+ ["jac", "add", "--cl", "lodash"],
583
+ capture_output=True,
584
+ text=True,
585
+ )
586
+
587
+ # Should fail with non-zero exit code
588
+ assert result.returncode != 0
589
+ assert "No jac.toml found" in result.stderr
590
+
591
+ finally:
592
+ os.chdir(original_cwd)
593
+
594
+
595
+ def test_uninstall_without_cl_flag() -> None:
596
+ """Test remove command without --cl flag should fail when no jac.toml exists."""
597
+ with tempfile.TemporaryDirectory() as temp_dir:
598
+ original_cwd = os.getcwd()
599
+ try:
600
+ os.chdir(temp_dir)
601
+
602
+ # Run remove command without --cl flag and without jac.toml
603
+ result = run(
604
+ ["jac", "remove", "lodash"],
605
+ capture_output=True,
606
+ text=True,
607
+ )
608
+
609
+ # Should fail with non-zero exit code because no jac.toml
610
+ assert result.returncode != 0
611
+ assert "No jac.toml found" in result.stderr
612
+
613
+ finally:
614
+ os.chdir(original_cwd)
615
+
616
+
617
+ def test_uninstall_without_package_name() -> None:
618
+ """Test remove --cl command without package name should fail."""
619
+ with tempfile.TemporaryDirectory() as temp_dir:
620
+ original_cwd = os.getcwd()
621
+ try:
622
+ os.chdir(temp_dir)
623
+
624
+ # Create jac.toml
625
+ _create_jac_toml(temp_dir)
626
+
627
+ # Run remove --cl command without package name
628
+ result = run(
629
+ ["jac", "remove", "--cl"],
630
+ capture_output=True,
631
+ text=True,
632
+ )
633
+
634
+ # Should fail with non-zero exit code
635
+ assert result.returncode != 0
636
+ assert "No packages specified" in result.stderr
637
+
638
+ finally:
639
+ os.chdir(original_cwd)
640
+
641
+
642
+ def test_uninstall_package_from_dependencies() -> None:
643
+ """Test remove --cl command removes package from dependencies."""
644
+ with tempfile.TemporaryDirectory() as temp_dir:
645
+ original_cwd = os.getcwd()
646
+ try:
647
+ os.chdir(temp_dir)
648
+
649
+ # Create jac.toml with a package
650
+ config_path = _create_jac_toml(temp_dir, deps='lodash = "^4.17.21"')
651
+
652
+ # Run remove --cl command
653
+ result = run(
654
+ ["jac", "remove", "--cl", "lodash"],
655
+ capture_output=True,
656
+ text=True,
657
+ )
658
+
659
+ # Should succeed
660
+ assert result.returncode == 0
661
+ assert "Removing lodash (npm)" in result.stdout
662
+ assert "Removed 1 package(s)" in result.stdout
663
+
664
+ # Verify package was removed from jac.toml
665
+ with open(config_path, "rb") as f:
666
+ updated_config = tomllib.load(f)
667
+
668
+ npm_deps = updated_config.get("dependencies", {}).get("npm", {})
669
+ regular_deps = {k: v for k, v in npm_deps.items() if k != "dev"}
670
+ assert "lodash" not in regular_deps
671
+
672
+ finally:
673
+ os.chdir(original_cwd)
674
+
675
+
676
+ def test_uninstall_package_from_devdependencies() -> None:
677
+ """Test remove --cl -d command removes package from dev-dependencies."""
678
+ with tempfile.TemporaryDirectory() as temp_dir:
679
+ original_cwd = os.getcwd()
680
+ try:
681
+ os.chdir(temp_dir)
682
+
683
+ # Create jac.toml with a dev-dependency
684
+ config_path = _create_jac_toml(
685
+ temp_dir, dev_deps='"@types/react" = "^18.0.0"'
686
+ )
687
+
688
+ # Run remove --cl -d command
689
+ result = run(
690
+ ["jac", "remove", "--cl", "-d", "@types/react"],
691
+ capture_output=True,
692
+ text=True,
693
+ )
694
+
695
+ # Should succeed
696
+ assert result.returncode == 0
697
+ assert "Removing @types/react (npm)" in result.stdout
698
+ assert "Removed 1 package(s)" in result.stdout
699
+
700
+ # Verify package was removed from jac.toml
701
+ with open(config_path, "rb") as f:
702
+ updated_config = tomllib.load(f)
703
+
704
+ npm_deps = updated_config.get("dependencies", {}).get("npm", {})
705
+ assert "@types/react" not in npm_deps.get("dev", {})
706
+
707
+ finally:
708
+ os.chdir(original_cwd)
709
+
710
+
711
+ def test_uninstall_nonexistent_package() -> None:
712
+ """Test remove --cl command with non-existent package should fail."""
713
+ with tempfile.TemporaryDirectory() as temp_dir:
714
+ original_cwd = os.getcwd()
715
+ try:
716
+ os.chdir(temp_dir)
717
+
718
+ # Create jac.toml without the package
719
+ _create_jac_toml(temp_dir)
720
+
721
+ # Run remove --cl command with non-existent package
722
+ result = run(
723
+ ["jac", "remove", "--cl", "nonexistent-package"],
724
+ capture_output=True,
725
+ text=True,
726
+ )
727
+
728
+ # Should fail with non-zero exit code
729
+ assert result.returncode != 0
730
+ assert "not found" in result.stderr.lower()
731
+
732
+ finally:
733
+ os.chdir(original_cwd)
734
+
735
+
736
+ def test_uninstall_without_config_toml() -> None:
737
+ """Test remove --cl command when jac.toml doesn't exist."""
738
+ with tempfile.TemporaryDirectory() as temp_dir:
739
+ original_cwd = os.getcwd()
740
+ try:
741
+ os.chdir(temp_dir)
742
+
743
+ # Run remove --cl command without jac.toml
744
+ result = run(
745
+ ["jac", "remove", "--cl", "lodash"],
746
+ capture_output=True,
747
+ text=True,
748
+ )
749
+
750
+ # Should fail with non-zero exit code
751
+ assert result.returncode != 0
752
+ assert "No jac.toml found" in result.stderr
753
+
754
+ finally:
755
+ os.chdir(original_cwd)