Skip to content

Commit ecf0b0e

Browse files
committed
Add automated route and frontend coverage
1 parent 84130bb commit ecf0b0e

File tree

9 files changed

+615
-4
lines changed

9 files changed

+615
-4
lines changed

.forgejo/workflows/ci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,6 @@ jobs:
3333
run: cargo build
3434

3535
- name: Run tests
36-
run: cargo test
36+
run: |
37+
cargo test
38+
node --test tests/*.test.js

.github/workflows/ci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,6 @@ jobs:
4747
run: cargo build
4848

4949
- name: Run tests
50-
run: cargo test
50+
run: |
51+
cargo test
52+
node --test tests/*.test.js

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,7 @@ exclude = [
2121
[dependencies]
2222
axum = "0.8"
2323
include_dir = "0.7"
24+
25+
[dev-dependencies]
26+
tokio = { version = "1", features = ["macros", "rt"] }
27+
tower = { version = "0.5", features = ["util"] }

Makefile

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ CSS_SRC := $(sort $(wildcard css-src/*.css))
2525
JS_SRC := $(sort $(wildcard js-src/*.js))
2626

2727
# ============== Phony Targets ==============
28-
.PHONY: banner help assets build build-release test test-quick test-doc test-unit test-one \
28+
.PHONY: banner help assets build build-release test test-quick test-doc test-unit test-frontend test-one \
2929
lint fmt fmt-check clippy ci-local pre-release version \
3030
bump-patch bump-minor bump-major bump-dry \
3131
publish-dry publish clean watch
@@ -80,6 +80,7 @@ test: banner
8080
@printf "$(CYAN)$(BOLD)╚══════════════════════════════════════╝$(RESET)\n\n"
8181
@printf "$(ARROW) $(BOLD)Running all tests...$(RESET)\n"
8282
@cargo test && \
83+
node --test tests/*.test.js && \
8384
printf "\n$(GREEN)$(CHECK) All tests passed$(RESET)\n\n" || \
8485
(printf "\n$(RED)$(CROSS) Tests failed$(RESET)\n\n" && exit 1)
8586

@@ -95,6 +96,10 @@ test-quick: banner
9596
@cargo test --lib --quiet && \
9697
printf "$(GREEN)$(CHECK) Unit tests passed$(RESET)\n\n" || \
9798
(printf "$(RED)$(CROSS) Unit tests failed$(RESET)\n\n" && exit 1)
99+
@printf "$(PROGRESS) Running frontend tests...\n"
100+
@node --test tests/*.test.js && \
101+
printf "$(GREEN)$(CHECK) Frontend tests passed$(RESET)\n\n" || \
102+
(printf "$(RED)$(CROSS) Frontend tests failed$(RESET)\n\n" && exit 1)
98103

99104
test-doc:
100105
@printf "$(PROGRESS) Running doctests...\n"
@@ -108,6 +113,12 @@ test-unit:
108113
printf "$(GREEN)$(CHECK) Unit tests passed$(RESET)\n" || \
109114
(printf "$(RED)$(CROSS) Unit tests failed$(RESET)\n" && exit 1)
110115

116+
test-frontend:
117+
@printf "$(PROGRESS) Running frontend tests...\n"
118+
@node --test tests/*.test.js && \
119+
printf "$(GREEN)$(CHECK) Frontend tests passed$(RESET)\n" || \
120+
(printf "$(RED)$(CROSS) Frontend tests failed$(RESET)\n" && exit 1)
121+
111122
test-one:
112123
@printf "$(PROGRESS) Running test: $(YELLOW)$(TEST)$(RESET)\n"
113124
@RUST_LOG=info cargo test $(TEST) -- --nocapture
@@ -151,8 +162,10 @@ ci-local: banner
151162
@$(MAKE) clippy --no-print-directory
152163
@printf "$(PROGRESS) Step 5/6: Doctests...\n"
153164
@cargo test --doc --quiet && printf "$(GREEN)$(CHECK) Doctests passed$(RESET)\n"
154-
@printf "$(PROGRESS) Step 6/6: Unit tests...\n"
165+
@printf "$(PROGRESS) Step 6/7: Unit tests...\n"
155166
@cargo test --lib --quiet && printf "$(GREEN)$(CHECK) Unit tests passed$(RESET)\n"
167+
@printf "$(PROGRESS) Step 7/7: Frontend tests...\n"
168+
@node --test tests/*.test.js && printf "$(GREEN)$(CHECK) Frontend tests passed$(RESET)\n"
156169
@printf "\n$(GREEN)$(BOLD)╔══════════════════════════════════════════════════════════╗$(RESET)\n"
157170
@printf "$(GREEN)$(BOLD)$(CHECK) CI SIMULATION PASSED ║$(RESET)\n"
158171
@printf "$(GREEN)$(BOLD)╚══════════════════════════════════════════════════════════╝$(RESET)\n\n"
@@ -252,6 +265,7 @@ help: banner
252265
@/bin/echo -e " $(GREEN)make test-quick$(RESET) - Run doctests + unit tests (fast)"
253266
@/bin/echo -e " $(GREEN)make test-doc$(RESET) - Run doctests only"
254267
@/bin/echo -e " $(GREEN)make test-unit$(RESET) - Run unit tests only"
268+
@/bin/echo -e " $(GREEN)make test-frontend$(RESET) - Run frontend Node tests"
255269
@/bin/echo -e " $(GREEN)make test-one TEST=name$(RESET) - Run specific test with output"
256270
@/bin/echo -e ""
257271
@/bin/echo -e "$(CYAN)$(BOLD)Lint & Format:$(RESET)"

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ Every backend element has a corresponding UI element. The library grows
3939
alongside the solver. When you scaffold a new SolverForge project with
4040
`solverforge new`, it's already wired in.
4141

42+
## Testing
43+
44+
Repository coverage now includes the embedded Rust asset routes plus Node-based
45+
frontend tests for backend adapters, solver lifecycle transitions, and core
46+
component rendering. Use `make test` for the full suite, `make test-quick` for
47+
Rust doctests and unit tests plus frontend coverage, or `make test-frontend`
48+
when you only want the JavaScript suite.
49+
4250
## Quick Start
4351

4452
```html

src/lib.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,99 @@ fn mime_from_path(path: &str) -> &'static str {
5555
fn is_immutable(path: &str) -> bool {
5656
path.starts_with("fonts/") || path.starts_with("vendor/") || path.starts_with("img/")
5757
}
58+
59+
#[cfg(test)]
60+
mod tests {
61+
use super::*;
62+
use axum::{
63+
body::{to_bytes, Body},
64+
http::{header, Request},
65+
};
66+
use tower::ServiceExt;
67+
68+
#[tokio::test]
69+
async fn serves_top_level_assets_with_short_cache_and_expected_mime() {
70+
let response = routes()
71+
.oneshot(
72+
Request::builder()
73+
.uri("/sf/sf.css")
74+
.body(Body::empty())
75+
.unwrap(),
76+
)
77+
.await
78+
.unwrap();
79+
80+
assert_eq!(response.status(), StatusCode::OK);
81+
assert_eq!(
82+
response.headers().get(header::CONTENT_TYPE).unwrap(),
83+
"text/css; charset=utf-8"
84+
);
85+
assert_eq!(
86+
response.headers().get(header::CACHE_CONTROL).unwrap(),
87+
"public, max-age=3600"
88+
);
89+
90+
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
91+
let css = String::from_utf8(body.to_vec()).unwrap();
92+
assert!(css.contains("--sf-emerald-50"));
93+
assert!(css.contains(".sf-gantt-split"));
94+
}
95+
96+
#[tokio::test]
97+
async fn serves_immutable_assets_with_long_cache_and_expected_mime() {
98+
let image = routes()
99+
.oneshot(
100+
Request::builder()
101+
.uri("/sf/img/ouroboros.svg")
102+
.body(Body::empty())
103+
.unwrap(),
104+
)
105+
.await
106+
.unwrap();
107+
108+
assert_eq!(image.status(), StatusCode::OK);
109+
assert_eq!(
110+
image.headers().get(header::CONTENT_TYPE).unwrap(),
111+
"image/svg+xml"
112+
);
113+
assert_eq!(
114+
image.headers().get(header::CACHE_CONTROL).unwrap(),
115+
"public, max-age=31536000, immutable"
116+
);
117+
118+
let vendor = routes()
119+
.oneshot(
120+
Request::builder()
121+
.uri("/sf/vendor/frappe-gantt/frappe-gantt.min.js")
122+
.body(Body::empty())
123+
.unwrap(),
124+
)
125+
.await
126+
.unwrap();
127+
128+
assert_eq!(vendor.status(), StatusCode::OK);
129+
assert_eq!(
130+
vendor.headers().get(header::CONTENT_TYPE).unwrap(),
131+
"application/javascript; charset=utf-8"
132+
);
133+
assert_eq!(
134+
vendor.headers().get(header::CACHE_CONTROL).unwrap(),
135+
"public, max-age=31536000, immutable"
136+
);
137+
}
138+
139+
#[tokio::test]
140+
async fn returns_not_found_for_missing_assets() {
141+
let response = routes()
142+
.oneshot(
143+
Request::builder()
144+
.uri("/sf/does-not-exist.js")
145+
.body(Body::empty())
146+
.unwrap(),
147+
)
148+
.await
149+
.unwrap();
150+
151+
assert_eq!(response.status(), StatusCode::NOT_FOUND);
152+
}
153+
}

0 commit comments

Comments
 (0)