jac-client 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jac_client/docs/README.md +50 -20
- jac_client/docs/advanced-state.md +13 -14
- jac_client/docs/asset-serving/intro.md +209 -0
- jac_client/docs/assets/pipe_line-v2.svg +32 -0
- jac_client/docs/file-system/app.jac.md +121 -0
- jac_client/docs/file-system/backend-frontend.md +217 -0
- jac_client/docs/file-system/intro.md +72 -0
- jac_client/docs/file-system/nested-imports.md +348 -0
- jac_client/docs/guide-example/intro.md +11 -13
- jac_client/docs/guide-example/step-01-setup.md +30 -20
- jac_client/docs/guide-example/step-02-components.md +24 -24
- jac_client/docs/guide-example/step-03-styling.md +24 -24
- jac_client/docs/guide-example/step-04-todo-ui.md +17 -17
- jac_client/docs/guide-example/step-05-local-state.md +23 -23
- jac_client/docs/guide-example/step-06-events.md +23 -24
- jac_client/docs/guide-example/step-07-effects.md +27 -28
- jac_client/docs/guide-example/step-08-walkers.md +23 -23
- jac_client/docs/guide-example/step-09-authentication.md +18 -18
- jac_client/docs/guide-example/step-10-routing.md +20 -21
- jac_client/docs/guide-example/step-11-final.md +34 -35
- jac_client/docs/imports.md +4 -5
- jac_client/docs/lifecycle-hooks.md +12 -13
- jac_client/docs/routing.md +21 -22
- jac_client/docs/styling/intro.md +249 -0
- jac_client/docs/styling/js-styling.md +367 -0
- jac_client/docs/styling/material-ui.md +341 -0
- jac_client/docs/styling/pure-css.md +299 -0
- jac_client/docs/styling/sass.md +403 -0
- jac_client/docs/styling/styled-components.md +395 -0
- jac_client/docs/styling/tailwind.md +298 -0
- jac_client/examples/all-in-one/.babelrc +9 -0
- jac_client/examples/all-in-one/README.md +16 -0
- jac_client/examples/all-in-one/app.jac +426 -0
- jac_client/examples/all-in-one/assets/burger.png +0 -0
- jac_client/examples/all-in-one/button.jac +7 -0
- jac_client/examples/all-in-one/components/button.jac +7 -0
- jac_client/examples/all-in-one/package.json +29 -0
- jac_client/examples/all-in-one/styles.css +26 -0
- jac_client/examples/all-in-one/vite.config.js +28 -0
- jac_client/examples/asset-serving/css-with-image/.babelrc +9 -0
- jac_client/examples/asset-serving/css-with-image/README.md +91 -0
- jac_client/examples/asset-serving/css-with-image/app.jac +88 -0
- jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
- jac_client/examples/asset-serving/css-with-image/package.json +28 -0
- jac_client/examples/asset-serving/css-with-image/styles.css +26 -0
- jac_client/examples/asset-serving/css-with-image/vite.config.js +28 -0
- jac_client/examples/asset-serving/image-asset/.babelrc +9 -0
- jac_client/examples/asset-serving/image-asset/README.md +119 -0
- jac_client/examples/asset-serving/image-asset/app.jac +55 -0
- jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
- jac_client/examples/asset-serving/image-asset/package.json +28 -0
- jac_client/examples/asset-serving/image-asset/styles.css +26 -0
- jac_client/examples/asset-serving/image-asset/vite.config.js +28 -0
- jac_client/examples/asset-serving/import-alias/.babelrc +9 -0
- jac_client/examples/asset-serving/import-alias/README.md +83 -0
- jac_client/examples/asset-serving/import-alias/app.jac +111 -0
- jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
- jac_client/examples/asset-serving/import-alias/package.json +28 -0
- jac_client/examples/asset-serving/import-alias/vite.config.js +28 -0
- jac_client/examples/basic/app.jac +14 -9
- jac_client/examples/basic/package.json +1 -1
- jac_client/examples/basic/vite.config.js +0 -1
- jac_client/examples/basic-auth/package.json +1 -1
- jac_client/examples/basic-auth/vite.config.js +0 -1
- jac_client/examples/basic-auth-with-router/package.json +1 -1
- jac_client/examples/basic-auth-with-router/vite.config.js +0 -1
- jac_client/examples/basic-full-stack/package.json +1 -1
- jac_client/examples/basic-full-stack/vite.config.js +0 -1
- jac_client/examples/css-styling/js-styling/.babelrc +9 -0
- jac_client/examples/css-styling/js-styling/README.md +183 -0
- jac_client/examples/css-styling/js-styling/app.jac +84 -0
- jac_client/examples/css-styling/js-styling/package.json +28 -0
- jac_client/examples/css-styling/js-styling/styles.js +100 -0
- jac_client/examples/css-styling/js-styling/vite.config.js +27 -0
- jac_client/examples/css-styling/material-ui/.babelrc +9 -0
- jac_client/examples/css-styling/material-ui/README.md +16 -0
- jac_client/examples/css-styling/material-ui/app.jac +122 -0
- jac_client/examples/css-styling/material-ui/package.json +32 -0
- jac_client/examples/css-styling/material-ui/vite.config.js +27 -0
- jac_client/examples/css-styling/pure-css/.babelrc +9 -0
- jac_client/examples/css-styling/pure-css/README.md +16 -0
- jac_client/examples/css-styling/pure-css/app.jac +64 -0
- jac_client/examples/css-styling/pure-css/package.json +28 -0
- jac_client/examples/css-styling/pure-css/styles.css +111 -0
- jac_client/examples/css-styling/pure-css/vite.config.js +27 -0
- jac_client/examples/css-styling/sass-example/.babelrc +9 -0
- jac_client/examples/css-styling/sass-example/README.md +16 -0
- jac_client/examples/css-styling/sass-example/app.jac +64 -0
- jac_client/examples/css-styling/sass-example/package.json +29 -0
- jac_client/examples/css-styling/sass-example/styles.scss +153 -0
- jac_client/examples/css-styling/sass-example/vite.config.js +27 -0
- jac_client/examples/css-styling/styled-components/.babelrc +9 -0
- jac_client/examples/css-styling/styled-components/README.md +16 -0
- jac_client/examples/css-styling/styled-components/app.jac +71 -0
- jac_client/examples/css-styling/styled-components/package.json +29 -0
- jac_client/examples/css-styling/styled-components/styled.js +90 -0
- jac_client/examples/css-styling/styled-components/vite.config.js +27 -0
- jac_client/examples/css-styling/tailwind-example/.babelrc +9 -0
- jac_client/examples/css-styling/tailwind-example/README.md +16 -0
- jac_client/examples/css-styling/tailwind-example/app.jac +63 -0
- jac_client/examples/css-styling/tailwind-example/global.css +1 -0
- jac_client/examples/css-styling/tailwind-example/package.json +30 -0
- jac_client/examples/css-styling/tailwind-example/vite.config.js +29 -0
- jac_client/examples/full-stack-with-auth/app.jac +20 -33
- jac_client/examples/full-stack-with-auth/package.json +1 -1
- jac_client/examples/full-stack-with-auth/vite.config.js +0 -1
- jac_client/examples/little-x/app.jac +327 -218
- jac_client/examples/little-x/submit-button.jac +1 -1
- jac_client/examples/nested-folders/nested-advance/.babelrc +9 -0
- jac_client/examples/nested-folders/nested-advance/ButtonRoot.jac +11 -0
- jac_client/examples/nested-folders/nested-advance/README.md +77 -0
- jac_client/examples/nested-folders/nested-advance/app.jac +35 -0
- jac_client/examples/nested-folders/nested-advance/level1/ButtonSecondL.jac +19 -0
- jac_client/examples/nested-folders/nested-advance/level1/Card.jac +43 -0
- jac_client/examples/nested-folders/nested-advance/level1/level2/ButtonThirdL.jac +25 -0
- jac_client/examples/nested-folders/nested-advance/package.json +29 -0
- jac_client/examples/nested-folders/nested-advance/vite.config.js +28 -0
- jac_client/examples/nested-folders/nested-basic/.babelrc +9 -0
- jac_client/examples/nested-folders/nested-basic/README.md +183 -0
- jac_client/examples/nested-folders/nested-basic/app.jac +13 -0
- jac_client/examples/nested-folders/nested-basic/app.js +7 -0
- jac_client/examples/nested-folders/nested-basic/button.jac +7 -0
- jac_client/examples/nested-folders/nested-basic/components/button.jac +7 -0
- jac_client/examples/nested-folders/nested-basic/package.json +28 -0
- jac_client/examples/nested-folders/nested-basic/vite.config.js +27 -0
- jac_client/examples/with-router/app.jac +1 -1
- jac_client/examples/with-router/package.json +1 -1
- jac_client/examples/with-router/vite.config.js +0 -1
- jac_client/plugin/cli.py +7 -2
- jac_client/plugin/client.py +68 -5
- jac_client/plugin/client_runtime.jac +1 -1
- jac_client/plugin/vite_client_bundle.py +162 -14
- jac_client/tests/__init__.py +0 -1
- jac_client/tests/fixtures/basic-app/app.jac +7 -2
- jac_client/tests/fixtures/cl_file/app.cl.jac +48 -0
- jac_client/tests/fixtures/cl_file/app.jac +15 -0
- jac_client/tests/fixtures/client_app_with_antd/app.jac +14 -8
- jac_client/tests/fixtures/js_import/app.jac +19 -15
- jac_client/tests/fixtures/js_import/utils.js +0 -1
- jac_client/tests/fixtures/package.json +1 -1
- jac_client/tests/fixtures/relative_import/app.jac +4 -6
- jac_client/tests/fixtures/relative_import/button.jac +7 -6
- jac_client/tests/fixtures/spawn_test/app.jac +1 -5
- jac_client/tests/fixtures/test_fragments_spread/app.jac +24 -10
- jac_client/tests/test_asset_examples.py +322 -0
- jac_client/tests/test_cl.py +480 -426
- jac_client/tests/test_create_jac_app.py +125 -133
- jac_client/tests/test_it.py +329 -0
- jac_client/tests/test_nested_file.py +374 -0
- {jac_client-0.2.0.dist-info → jac_client-0.2.2.dist-info}/METADATA +2 -2
- jac_client-0.2.2.dist-info/RECORD +171 -0
- jac_client-0.2.0.dist-info/RECORD +0 -72
- {jac_client-0.2.0.dist-info → jac_client-0.2.2.dist-info}/WHEEL +0 -0
- {jac_client-0.2.0.dist-info → jac_client-0.2.2.dist-info}/entry_points.txt +0 -0
|
@@ -4,136 +4,128 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
import tempfile
|
|
6
6
|
from subprocess import run
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
# Should fail with non-zero exit code
|
|
135
|
-
self.assertNotEqual(result.returncode, 0)
|
|
136
|
-
self.assertIn(f"Directory '{test_project_name}' already exists", result.stderr)
|
|
137
|
-
|
|
138
|
-
finally:
|
|
139
|
-
os.chdir(original_cwd)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_create_jac_app() -> None:
|
|
10
|
+
"""Test create-jac-app 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 create-jac-app command
|
|
21
|
+
result = run(
|
|
22
|
+
["jac", "create_jac_app", test_project_name],
|
|
23
|
+
capture_output=True,
|
|
24
|
+
text=True,
|
|
25
|
+
check=True,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Check that command succeeded
|
|
29
|
+
assert result.returncode == 0
|
|
30
|
+
assert (
|
|
31
|
+
f"Successfully created Jac application '{test_project_name}'!"
|
|
32
|
+
in result.stdout
|
|
33
|
+
)
|
|
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 package.json was created and has correct content
|
|
41
|
+
package_json_path = os.path.join(project_path, "package.json")
|
|
42
|
+
assert os.path.exists(package_json_path)
|
|
43
|
+
|
|
44
|
+
with open(package_json_path) as f:
|
|
45
|
+
package_data = json.load(f)
|
|
46
|
+
|
|
47
|
+
assert package_data["name"] == test_project_name
|
|
48
|
+
assert package_data["type"] == "module"
|
|
49
|
+
assert "vite" in package_data["devDependencies"]
|
|
50
|
+
assert "build" in package_data["scripts"]
|
|
51
|
+
assert "dev" in package_data["scripts"]
|
|
52
|
+
assert "preview" in package_data["scripts"]
|
|
53
|
+
|
|
54
|
+
# Verify app.jac file was created
|
|
55
|
+
app_jac_path = os.path.join(project_path, "app.jac")
|
|
56
|
+
assert os.path.exists(app_jac_path)
|
|
57
|
+
|
|
58
|
+
with open(app_jac_path) as f:
|
|
59
|
+
app_jac_content = f.read()
|
|
60
|
+
|
|
61
|
+
assert "app()" in app_jac_content
|
|
62
|
+
|
|
63
|
+
# Verify README.md was created
|
|
64
|
+
readme_path = os.path.join(project_path, "README.md")
|
|
65
|
+
assert os.path.exists(readme_path)
|
|
66
|
+
|
|
67
|
+
with open(readme_path) as f:
|
|
68
|
+
readme_content = f.read()
|
|
69
|
+
|
|
70
|
+
assert f"# {test_project_name}" in readme_content
|
|
71
|
+
assert "jac serve app.jac" in readme_content
|
|
72
|
+
|
|
73
|
+
# Verify node_modules was created (npm install ran)
|
|
74
|
+
node_modules_path = os.path.join(project_path, "node_modules")
|
|
75
|
+
assert os.path.exists(node_modules_path)
|
|
76
|
+
|
|
77
|
+
finally:
|
|
78
|
+
# Return to original directory
|
|
79
|
+
os.chdir(original_cwd)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_create_jac_app_invalid_name() -> None:
|
|
83
|
+
"""Test create-jac-app command with invalid project name."""
|
|
84
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
85
|
+
original_cwd = os.getcwd()
|
|
86
|
+
try:
|
|
87
|
+
os.chdir(temp_dir)
|
|
88
|
+
|
|
89
|
+
# Test with invalid name containing spaces
|
|
90
|
+
result = run(
|
|
91
|
+
["jac", "create_jac_app", "invalid name with spaces"],
|
|
92
|
+
capture_output=True,
|
|
93
|
+
text=True,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Should fail with non-zero exit code
|
|
97
|
+
assert result.returncode != 0
|
|
98
|
+
assert (
|
|
99
|
+
"Project name must contain only letters, numbers, hyphens, and underscores"
|
|
100
|
+
in result.stderr
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
finally:
|
|
104
|
+
os.chdir(original_cwd)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_create_jac_app_existing_directory() -> None:
|
|
108
|
+
"""Test create-jac-app command when directory already exists."""
|
|
109
|
+
test_project_name = "existing-test-app"
|
|
110
|
+
|
|
111
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
112
|
+
original_cwd = os.getcwd()
|
|
113
|
+
try:
|
|
114
|
+
os.chdir(temp_dir)
|
|
115
|
+
|
|
116
|
+
# Create the directory first
|
|
117
|
+
os.makedirs(test_project_name)
|
|
118
|
+
|
|
119
|
+
# Try to create app with same name
|
|
120
|
+
result = run(
|
|
121
|
+
["jac", "create_jac_app", test_project_name],
|
|
122
|
+
capture_output=True,
|
|
123
|
+
text=True,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Should fail with non-zero exit code
|
|
127
|
+
assert result.returncode != 0
|
|
128
|
+
assert f"Directory '{test_project_name}' already exists" in result.stderr
|
|
129
|
+
|
|
130
|
+
finally:
|
|
131
|
+
os.chdir(original_cwd)
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""End-to-end tests for `jac serve` HTTP endpoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import socket
|
|
9
|
+
import tempfile
|
|
10
|
+
import time
|
|
11
|
+
from subprocess import Popen, run
|
|
12
|
+
from urllib.error import HTTPError, URLError
|
|
13
|
+
from urllib.request import Request, urlopen
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _wait_for_port(
|
|
19
|
+
host: str,
|
|
20
|
+
port: int,
|
|
21
|
+
timeout: float = 60.0,
|
|
22
|
+
poll_interval: float = 0.5,
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Block until a TCP port is accepting connections or timeout.
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
TimeoutError: if the port is not accepting connections within timeout.
|
|
28
|
+
"""
|
|
29
|
+
deadline = time.time() + timeout
|
|
30
|
+
last_err: Exception | None = None
|
|
31
|
+
|
|
32
|
+
while time.time() < deadline:
|
|
33
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
34
|
+
sock.settimeout(poll_interval)
|
|
35
|
+
try:
|
|
36
|
+
sock.connect((host, port))
|
|
37
|
+
return
|
|
38
|
+
except OSError as exc: # Connection refused / timeout
|
|
39
|
+
last_err = exc
|
|
40
|
+
time.sleep(poll_interval)
|
|
41
|
+
|
|
42
|
+
raise TimeoutError(
|
|
43
|
+
f"Timed out waiting for {host}:{port} to become available. Last error: {last_err}"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_all_in_one_app_endpoints() -> None:
|
|
48
|
+
"""Create a Jac app, copy @all-in-one into it, run npm install, then verify endpoints."""
|
|
49
|
+
print(
|
|
50
|
+
"[DEBUG] Starting test_all_in_one_app_endpoints using jac create_jac_app + @all-in-one"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Resolve the path to jac_client/examples/all-in-one relative to this test file.
|
|
54
|
+
tests_dir = os.path.dirname(__file__)
|
|
55
|
+
jac_client_root = os.path.dirname(tests_dir)
|
|
56
|
+
all_in_one_path = os.path.join(jac_client_root, "examples", "all-in-one")
|
|
57
|
+
|
|
58
|
+
print(f"[DEBUG] Resolved all-in-one source path: {all_in_one_path}")
|
|
59
|
+
assert os.path.isdir(all_in_one_path), "all-in-one example directory missing"
|
|
60
|
+
|
|
61
|
+
app_name = "e2e-all-in-one-app"
|
|
62
|
+
|
|
63
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
64
|
+
print(f"[DEBUG] Created temporary directory at {temp_dir}")
|
|
65
|
+
original_cwd = os.getcwd()
|
|
66
|
+
try:
|
|
67
|
+
os.chdir(temp_dir)
|
|
68
|
+
print(f"[DEBUG] Changed working directory to {temp_dir}")
|
|
69
|
+
|
|
70
|
+
# 1. Create a new Jac app via CLI (requires jac + jac-client plugin installed)
|
|
71
|
+
print(f"[DEBUG] Running 'jac create_jac_app {app_name}'")
|
|
72
|
+
create_result = run(
|
|
73
|
+
["jac", "create_jac_app", app_name],
|
|
74
|
+
capture_output=True,
|
|
75
|
+
text=True,
|
|
76
|
+
)
|
|
77
|
+
print(
|
|
78
|
+
"[DEBUG] 'jac create_jac_app' completed "
|
|
79
|
+
f"returncode={create_result.returncode}\n"
|
|
80
|
+
f"STDOUT:\n{create_result.stdout}\n"
|
|
81
|
+
f"STDERR:\n{create_result.stderr}\n"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# If the currently installed `jac` CLI does not support `create_jac_app`,
|
|
85
|
+
# skip this integration test instead of failing the whole suite.
|
|
86
|
+
if (
|
|
87
|
+
create_result.returncode != 0
|
|
88
|
+
and "invalid choice: 'create_jac_app'" in create_result.stderr
|
|
89
|
+
):
|
|
90
|
+
pytest.skip(
|
|
91
|
+
"Skipping: installed `jac` CLI does not support `create_jac_app`."
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
assert create_result.returncode == 0, (
|
|
95
|
+
"jac create_jac_app failed\n"
|
|
96
|
+
f"STDOUT:\n{create_result.stdout}\n"
|
|
97
|
+
f"STDERR:\n{create_result.stderr}\n"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
project_path = os.path.join(temp_dir, app_name)
|
|
101
|
+
print(f"[DEBUG] Created base Jac app at {project_path}")
|
|
102
|
+
assert os.path.isdir(project_path)
|
|
103
|
+
|
|
104
|
+
# 2. Copy the contents from @all-in-one into the created app directory.
|
|
105
|
+
print("[DEBUG] Copying @all-in-one contents into created Jac app")
|
|
106
|
+
for entry in os.listdir(all_in_one_path):
|
|
107
|
+
src = os.path.join(all_in_one_path, entry)
|
|
108
|
+
dst = os.path.join(project_path, entry)
|
|
109
|
+
# Avoid copying node_modules / build artifacts from the example.
|
|
110
|
+
if entry in {"node_modules", "build", "dist", ".pytest_cache"}:
|
|
111
|
+
continue
|
|
112
|
+
if os.path.isdir(src):
|
|
113
|
+
shutil.copytree(src, dst, dirs_exist_ok=True)
|
|
114
|
+
else:
|
|
115
|
+
shutil.copy2(src, dst)
|
|
116
|
+
|
|
117
|
+
# 3. Run `npm install` inside the project directory so the frontend can build.
|
|
118
|
+
print("[DEBUG] Running 'npm install' in created Jac app")
|
|
119
|
+
npm_result = run(
|
|
120
|
+
["npm", "install"],
|
|
121
|
+
cwd=project_path,
|
|
122
|
+
capture_output=True,
|
|
123
|
+
text=True,
|
|
124
|
+
)
|
|
125
|
+
print(
|
|
126
|
+
"[DEBUG] 'npm install' completed "
|
|
127
|
+
f"returncode={npm_result.returncode}\n"
|
|
128
|
+
f"STDOUT (truncated to 2000 chars):\n{npm_result.stdout[:2000]}\n"
|
|
129
|
+
f"STDERR (truncated to 2000 chars):\n{npm_result.stderr[:2000]}\n"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if npm_result.returncode != 0:
|
|
133
|
+
pytest.skip(
|
|
134
|
+
"Skipping: npm install failed or npm is not available in PATH."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
app_jac_path = os.path.join(project_path, "app.jac")
|
|
138
|
+
assert os.path.isfile(app_jac_path), "all-in-one app.jac file missing"
|
|
139
|
+
|
|
140
|
+
# 4. Start the server: `jac serve app.jac`
|
|
141
|
+
# NOTE: We don't use text mode here, so `Popen` defaults to bytes.
|
|
142
|
+
# Use `Popen[bytes]` in the type annotation to keep mypy happy.
|
|
143
|
+
server: Popen[bytes] | None = None
|
|
144
|
+
try:
|
|
145
|
+
print("[DEBUG] Starting server with 'jac serve app.jac'")
|
|
146
|
+
server = Popen(
|
|
147
|
+
["jac", "serve", "app.jac"],
|
|
148
|
+
cwd=project_path,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Wait for localhost:8000 to become available
|
|
152
|
+
print("[DEBUG] Waiting for server to be available on 127.0.0.1:8000")
|
|
153
|
+
_wait_for_port("127.0.0.1", 8000, timeout=90.0)
|
|
154
|
+
print("[DEBUG] Server is now accepting connections on 127.0.0.1:8000")
|
|
155
|
+
|
|
156
|
+
# "/" – server up
|
|
157
|
+
try:
|
|
158
|
+
print("[DEBUG] Sending GET request to root endpoint /")
|
|
159
|
+
with urlopen(
|
|
160
|
+
"http://127.0.0.1:8000",
|
|
161
|
+
timeout=10,
|
|
162
|
+
) as resp_root:
|
|
163
|
+
root_body = resp_root.read().decode("utf-8", errors="ignore")
|
|
164
|
+
print(
|
|
165
|
+
"[DEBUG] Received response from root endpoint /\n"
|
|
166
|
+
f"Status: {resp_root.status}\n"
|
|
167
|
+
f"Body (truncated to 500 chars):\n{root_body[:500]}"
|
|
168
|
+
)
|
|
169
|
+
assert resp_root.status == 200
|
|
170
|
+
assert '"Jac API Server"' in root_body
|
|
171
|
+
assert '"endpoints"' in root_body
|
|
172
|
+
except (URLError, HTTPError) as exc:
|
|
173
|
+
print(f"[DEBUG] Error while requesting root endpoint: {exc}")
|
|
174
|
+
pytest.fail(f"Failed to GET root endpoint: {exc}")
|
|
175
|
+
|
|
176
|
+
# "/page/app" – main page is loading
|
|
177
|
+
try:
|
|
178
|
+
print("[DEBUG] Sending GET request to /page/app endpoint")
|
|
179
|
+
with urlopen(
|
|
180
|
+
"http://127.0.0.1:8000/page/app",
|
|
181
|
+
timeout=200,
|
|
182
|
+
) as resp_page:
|
|
183
|
+
page_body = resp_page.read().decode("utf-8", errors="ignore")
|
|
184
|
+
print(
|
|
185
|
+
"[DEBUG] Received response from /page/app endpoint\n"
|
|
186
|
+
f"Status: {resp_page.status}\n"
|
|
187
|
+
f"Body (truncated to 500 chars):\n{page_body[:500]}"
|
|
188
|
+
)
|
|
189
|
+
assert resp_page.status == 200
|
|
190
|
+
assert "<html" in page_body.lower()
|
|
191
|
+
except (URLError, HTTPError) as exc:
|
|
192
|
+
print(f"[DEBUG] Error while requesting /page/app endpoint: {exc}")
|
|
193
|
+
pytest.fail("Failed to GET /page/app endpoint")
|
|
194
|
+
|
|
195
|
+
# "/page/app#/nested" – relative paths / nested route
|
|
196
|
+
# (hash fragment is client-side only but server should still serve the app shell)
|
|
197
|
+
try:
|
|
198
|
+
print("[DEBUG] Sending GET request to /page/app#/nested endpoint")
|
|
199
|
+
with urlopen(
|
|
200
|
+
"http://127.0.0.1:8000/page/app#/nested",
|
|
201
|
+
timeout=200,
|
|
202
|
+
) as resp_nested:
|
|
203
|
+
nested_body = resp_nested.read().decode(
|
|
204
|
+
"utf-8", errors="ignore"
|
|
205
|
+
)
|
|
206
|
+
print(
|
|
207
|
+
"[DEBUG] Received response from /page/app#/nested endpoint\n"
|
|
208
|
+
f"Status: {resp_nested.status}\n"
|
|
209
|
+
f"Body (truncated to 500 chars):\n{nested_body[:500]}"
|
|
210
|
+
)
|
|
211
|
+
assert resp_nested.status == 200
|
|
212
|
+
assert "<html" in nested_body.lower()
|
|
213
|
+
except (URLError, HTTPError) as exc:
|
|
214
|
+
print(
|
|
215
|
+
f"[DEBUG] Error while requesting /page/app#/nested endpoint: {exc}"
|
|
216
|
+
)
|
|
217
|
+
pytest.fail("Failed to GET /page/app#/nested endpoint")
|
|
218
|
+
|
|
219
|
+
# "/static/main.css" – CSS compiled and serving
|
|
220
|
+
try:
|
|
221
|
+
print("[DEBUG] Sending GET request to /static/main.css")
|
|
222
|
+
with urlopen(
|
|
223
|
+
"http://127.0.0.1:8000/static/main.css",
|
|
224
|
+
timeout=20,
|
|
225
|
+
) as resp_css:
|
|
226
|
+
css_body = resp_css.read().decode("utf-8", errors="ignore")
|
|
227
|
+
print(
|
|
228
|
+
"[DEBUG] Received response from /static/main.css\n"
|
|
229
|
+
f"Status: {resp_css.status}\n"
|
|
230
|
+
f"Body (truncated to 500 chars):\n{css_body[:500]}"
|
|
231
|
+
)
|
|
232
|
+
assert resp_css.status == 200
|
|
233
|
+
assert len(css_body.strip()) > 0
|
|
234
|
+
except (URLError, HTTPError) as exc:
|
|
235
|
+
print(f"[DEBUG] Error while requesting /static/main.css: {exc}")
|
|
236
|
+
pytest.fail("Failed to GET /static/main.css")
|
|
237
|
+
|
|
238
|
+
# "/static/assets/burger.png" – static files are loading
|
|
239
|
+
try:
|
|
240
|
+
print("[DEBUG] Sending GET request to /static/assets/burger.png")
|
|
241
|
+
with urlopen(
|
|
242
|
+
"http://127.0.0.1:8000/static/assets/burger.png",
|
|
243
|
+
timeout=20,
|
|
244
|
+
) as resp_png:
|
|
245
|
+
png_bytes = resp_png.read()
|
|
246
|
+
print(
|
|
247
|
+
"[DEBUG] Received response from /static/assets/burger.png\n"
|
|
248
|
+
f"Status: {resp_png.status}\n"
|
|
249
|
+
f"Content-Length: {len(png_bytes)} bytes"
|
|
250
|
+
)
|
|
251
|
+
assert resp_png.status == 200
|
|
252
|
+
assert len(png_bytes) > 0
|
|
253
|
+
assert png_bytes.startswith(b"\x89PNG"), (
|
|
254
|
+
"Expected PNG signature at start of burger.png"
|
|
255
|
+
)
|
|
256
|
+
except (URLError, HTTPError) as exc:
|
|
257
|
+
print(
|
|
258
|
+
f"[DEBUG] Error while requesting /static/assets/burger.png: {exc}"
|
|
259
|
+
)
|
|
260
|
+
pytest.fail("Failed to GET /static/assets/burger.png")
|
|
261
|
+
|
|
262
|
+
# "/walker/get_server_message" – walkers are integrated and up and running
|
|
263
|
+
try:
|
|
264
|
+
print("[DEBUG] Sending GET request to /walker/get_server_message")
|
|
265
|
+
with urlopen(
|
|
266
|
+
"http://127.0.0.1:8000/walker/get_server_message",
|
|
267
|
+
timeout=20,
|
|
268
|
+
) as resp_walker:
|
|
269
|
+
walker_body = resp_walker.read().decode(
|
|
270
|
+
"utf-8", errors="ignore"
|
|
271
|
+
)
|
|
272
|
+
print(
|
|
273
|
+
"[DEBUG] Received response from /walker/get_server_message\n"
|
|
274
|
+
f"Status: {resp_walker.status}\n"
|
|
275
|
+
f"Body (truncated to 500 chars):\n{walker_body[:500]}"
|
|
276
|
+
)
|
|
277
|
+
assert resp_walker.status == 200
|
|
278
|
+
assert "get_server_message" in walker_body
|
|
279
|
+
except (URLError, HTTPError) as exc:
|
|
280
|
+
print(
|
|
281
|
+
f"[DEBUG] Error while requesting /walker/get_server_message: {exc}"
|
|
282
|
+
)
|
|
283
|
+
pytest.fail("Failed to GET /walker/get_server_message")
|
|
284
|
+
|
|
285
|
+
# POST /walker/create_todo – create a Todo via walker HTTP API
|
|
286
|
+
try:
|
|
287
|
+
print(
|
|
288
|
+
"[DEBUG] Sending POST request to /walker/create_todo endpoint"
|
|
289
|
+
)
|
|
290
|
+
payload = {
|
|
291
|
+
"text": "Sample todo from all-in-one app",
|
|
292
|
+
}
|
|
293
|
+
req = Request(
|
|
294
|
+
"http://127.0.0.1:8000/walker/create_todo",
|
|
295
|
+
data=json.dumps(payload).encode("utf-8"),
|
|
296
|
+
headers={"Content-Type": "application/json"},
|
|
297
|
+
method="POST",
|
|
298
|
+
)
|
|
299
|
+
with urlopen(req, timeout=20) as resp_create:
|
|
300
|
+
create_body = resp_create.read().decode(
|
|
301
|
+
"utf-8", errors="ignore"
|
|
302
|
+
)
|
|
303
|
+
print(
|
|
304
|
+
"[DEBUG] Received response from /walker/create_todo\n"
|
|
305
|
+
f"Status: {resp_create.status}\n"
|
|
306
|
+
f"Body (truncated to 500 chars):\n{create_body[:500]}"
|
|
307
|
+
)
|
|
308
|
+
assert resp_create.status == 200
|
|
309
|
+
# Basic sanity check: created Todo text should appear in the response payload.
|
|
310
|
+
assert "Sample todo from all-in-one app" in create_body
|
|
311
|
+
except (URLError, HTTPError) as exc:
|
|
312
|
+
print(f"[DEBUG] Error while requesting /walker/create_todo: {exc}")
|
|
313
|
+
pytest.fail("Failed to POST /walker/create_todo")
|
|
314
|
+
|
|
315
|
+
finally:
|
|
316
|
+
if server is not None:
|
|
317
|
+
print("[DEBUG] Terminating server process")
|
|
318
|
+
server.terminate()
|
|
319
|
+
try:
|
|
320
|
+
server.wait(timeout=15)
|
|
321
|
+
print("[DEBUG] Server process terminated cleanly")
|
|
322
|
+
except Exception:
|
|
323
|
+
print(
|
|
324
|
+
"[DEBUG] Server did not terminate cleanly, killing process"
|
|
325
|
+
)
|
|
326
|
+
server.kill()
|
|
327
|
+
finally:
|
|
328
|
+
print(f"[DEBUG] Restoring original working directory to {original_cwd}")
|
|
329
|
+
os.chdir(original_cwd)
|