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