create-caspian-app 1.3.6 → 1.3.7
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.
- package/dist/.github/copilot-instructions.md +5 -2
- package/dist/AGENTS.md +5 -1
- package/dist/index.js +1 -1
- package/dist/main.py +152 -156
- package/dist/public/js/pp-reactive-v2.min.js +1 -1
- package/dist/settings/_component_imports.py +1 -0
- package/dist/settings/_markup_equivalence.py +320 -0
- package/dist/settings/browser_log.py +13 -6
- package/dist/settings/build-static.py +5 -4
- package/dist/settings/check.py +6 -3
- package/dist/settings/check_templates.py +90 -21
- package/dist/settings/fix.py +23 -5
- package/dist/settings/format.py +721 -0
- package/dist/settings/serve-static.py +1 -2
- package/dist/src/app/index.py +67 -133
- package/dist/src/app/layout.py +15 -0
- package/dist/tests/README.md +119 -4
- package/dist/tests/conftest.py +1 -0
- package/dist/tests/test_health_route.py +1 -3
- package/dist/tests/test_main_helpers.py +4 -7
- package/package.json +1 -1
|
@@ -115,8 +115,7 @@ def _serve(host: str, start_port: int, tries: int) -> int:
|
|
|
115
115
|
last_error = exc
|
|
116
116
|
if offset == 0:
|
|
117
117
|
print(
|
|
118
|
-
f"{YELLOW} Port {start_port} is busy; searching for a free "
|
|
119
|
-
f"port...{RESET}"
|
|
118
|
+
f"{YELLOW} Port {start_port} is busy; searching for a free port...{RESET}"
|
|
120
119
|
)
|
|
121
120
|
continue
|
|
122
121
|
# A different error (bad host, permissions, etc.) is not recoverable
|
package/dist/src/app/index.py
CHANGED
|
@@ -3,163 +3,97 @@ from casp.component_decorator import html
|
|
|
3
3
|
|
|
4
4
|
def page():
|
|
5
5
|
return html(r"""
|
|
6
|
-
<
|
|
7
|
-
class="
|
|
8
|
-
>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<div
|
|
13
|
-
class="z-10 w-full max-w-5xl items-center justify-between font-mono text-sm lg:flex"
|
|
14
|
-
>
|
|
15
|
-
<p
|
|
16
|
-
class="fixed left-0 top-0 flex w-full justify-center border-b border-border bg-background/80 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-muted/30 lg:p-4 lg:dark:bg-zinc-800/30"
|
|
17
|
-
>
|
|
18
|
-
Get started by editing
|
|
19
|
-
<code class="font-bold text-primary">src/app/index.html</code>
|
|
6
|
+
<main class="flex min-h-screen flex-col justify-between gap-16 bg-background px-6 py-8 text-foreground md:px-12 md:py-12">
|
|
7
|
+
<header class="flex items-center justify-between gap-4">
|
|
8
|
+
<p class="border border-border px-4 py-2 font-mono text-xs text-muted-foreground">
|
|
9
|
+
Get started by editing
|
|
10
|
+
<code class="text-foreground">src/app/index.py</code>
|
|
20
11
|
</p>
|
|
21
12
|
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
target="_blank"
|
|
28
|
-
rel="noreferrer"
|
|
29
|
-
class="pointer-events-auto flex place-items-center gap-2 p-8 lg:p-0 hover:text-primary transition-colors"
|
|
30
|
-
>
|
|
31
|
-
<span class="font-brand text-xs font-bold tracking-widest"
|
|
32
|
-
>BUILT WITH CASPIAN</span
|
|
33
|
-
>
|
|
13
|
+
<a class="flex items-center gap-2 font-mono text-[11px] uppercase tracking-[0.18em] text-muted-foreground transition-colors hover:text-foreground"
|
|
14
|
+
href="https://github.com/TheSteelNinjaCode/create-caspian-app"
|
|
15
|
+
target="_blank"
|
|
16
|
+
rel="noreferrer">
|
|
17
|
+
<span class="hidden sm:inline">Built with Caspian</span>
|
|
34
18
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
</
|
|
47
|
-
</
|
|
48
|
-
</
|
|
19
|
+
<svg class="size-5"
|
|
20
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
21
|
+
viewBox="0 0 24 24"
|
|
22
|
+
fill="none"
|
|
23
|
+
stroke="currentColor"
|
|
24
|
+
stroke-width="2"
|
|
25
|
+
stroke-linecap="round"
|
|
26
|
+
stroke-linejoin="round"
|
|
27
|
+
aria-hidden="true">
|
|
28
|
+
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
|
|
29
|
+
<path d="M9 18c-4.51 2-5-2-7-2" />
|
|
30
|
+
</svg>
|
|
31
|
+
</a>
|
|
32
|
+
</header>
|
|
49
33
|
|
|
50
|
-
<div
|
|
51
|
-
class="
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class="relative flex flex-col items-center justify-center gap-6 z-10 pt-24 lg:pt-0 before:absolute before:h-75 before:w-120 before:-translate-x-1/2 before:rounded-full before:bg-linear-to-br before:from-transparent before:to-primary before:opacity-10 before:blur-2xl before:content-[''] before:max-w-full after:absolute after:-z-20 after:h-45 after:w-60 after:translate-x-1/3 after:bg-linear-to-t after:from-cyan-300 after:via-primary after:opacity-20 after:blur-2xl after:content-[''] after:max-w-full"
|
|
55
|
-
>
|
|
56
|
-
<div
|
|
57
|
-
class="relative w-24 h-24 md:w-32 md:h-32 flex items-center justify-center mb-4"
|
|
58
|
-
>
|
|
59
|
-
<img
|
|
60
|
-
src="/favicon.ico"
|
|
61
|
-
alt="caspian logo"
|
|
62
|
-
class="w-full h-full object-contain"
|
|
63
|
-
/>
|
|
64
|
-
</div>
|
|
34
|
+
<div class="flex flex-col items-center text-center">
|
|
35
|
+
<img class="size-20 object-contain md:size-24"
|
|
36
|
+
src="/favicon.ico"
|
|
37
|
+
alt="Caspian" />
|
|
65
38
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
</
|
|
71
|
-
</div>
|
|
72
|
-
<p
|
|
73
|
-
class="text-muted-foreground text-center max-w-[85vw] md:max-w-125 text-sm md:text-base"
|
|
74
|
-
>
|
|
75
|
-
The Native Python Framework with
|
|
76
|
-
<span class="text-[#053b69] font-medium whitespace-nowrap"
|
|
77
|
-
>PulsePoint</span
|
|
78
|
-
>
|
|
79
|
-
Reactivity.
|
|
39
|
+
<h1 class="mt-8 text-5xl font-bold tracking-[-0.04em] md:text-7xl">CASPIAN</h1>
|
|
40
|
+
|
|
41
|
+
<p class="mt-5 max-w-md text-pretty text-muted-foreground">
|
|
42
|
+
The native Python framework, with
|
|
43
|
+
<span class="text-foreground">PulsePoint</span> reactivity.
|
|
80
44
|
</p>
|
|
81
45
|
</div>
|
|
82
46
|
|
|
83
|
-
<
|
|
84
|
-
class="
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
class="
|
|
89
|
-
target="_blank"
|
|
90
|
-
>
|
|
91
|
-
<h2
|
|
92
|
-
class="mb-3 text-xl md:text-2xl font-semibold font-brand group-hover:text-primary transition-colors"
|
|
93
|
-
>
|
|
47
|
+
<nav class="grid gap-px border border-border bg-border sm:grid-cols-2 lg:grid-cols-4">
|
|
48
|
+
<a class="group bg-background p-6 transition-colors hover:bg-muted/50"
|
|
49
|
+
href="https://caspian.tsnc.tech/docs"
|
|
50
|
+
target="_blank"
|
|
51
|
+
rel="noreferrer">
|
|
52
|
+
<h2 class="flex items-center gap-2 font-semibold">
|
|
94
53
|
Docs
|
|
95
|
-
<span
|
|
96
|
-
class="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none"
|
|
97
|
-
>-></span
|
|
98
|
-
>
|
|
54
|
+
<span class="transition-transform group-hover:translate-x-1 motion-reduce:transform-none">→</span>
|
|
99
55
|
</h2>
|
|
100
|
-
<p class="
|
|
101
|
-
|
|
102
|
-
features.
|
|
56
|
+
<p class="mt-2 text-sm leading-relaxed text-muted-foreground">
|
|
57
|
+
Everything about routing, layouts, components and PulsePoint.
|
|
103
58
|
</p>
|
|
104
59
|
</a>
|
|
105
60
|
|
|
106
|
-
<a
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
<h2
|
|
112
|
-
class="mb-3 text-xl md:text-2xl font-semibold font-brand group-hover:text-cyan-400 transition-colors"
|
|
113
|
-
>
|
|
61
|
+
<a class="group bg-background p-6 transition-colors hover:bg-muted/50"
|
|
62
|
+
href="https://caspian.tsnc.tech/docs/rpc"
|
|
63
|
+
target="_blank"
|
|
64
|
+
rel="noreferrer">
|
|
65
|
+
<h2 class="flex items-center gap-2 font-semibold">
|
|
114
66
|
RPC
|
|
115
|
-
<span
|
|
116
|
-
class="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none"
|
|
117
|
-
>-></span
|
|
118
|
-
>
|
|
67
|
+
<span class="transition-transform group-hover:translate-x-1 motion-reduce:transform-none">→</span>
|
|
119
68
|
</h2>
|
|
120
|
-
<p class="
|
|
121
|
-
|
|
69
|
+
<p class="mt-2 text-sm leading-relaxed text-muted-foreground">
|
|
70
|
+
Call a Python function straight from a click, no endpoint to write.
|
|
122
71
|
</p>
|
|
123
72
|
</a>
|
|
124
73
|
|
|
125
|
-
<a
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
<h2
|
|
131
|
-
class="mb-3 text-xl md:text-2xl font-semibold font-brand group-hover:text-purple-400 transition-colors"
|
|
132
|
-
>
|
|
74
|
+
<a class="group bg-background p-6 transition-colors hover:bg-muted/50"
|
|
75
|
+
href="https://caspian.tsnc.tech/docs/database"
|
|
76
|
+
target="_blank"
|
|
77
|
+
rel="noreferrer">
|
|
78
|
+
<h2 class="flex items-center gap-2 font-semibold">
|
|
133
79
|
Schema
|
|
134
|
-
<span
|
|
135
|
-
class="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none"
|
|
136
|
-
>-></span
|
|
137
|
-
>
|
|
80
|
+
<span class="transition-transform group-hover:translate-x-1 motion-reduce:transform-none">→</span>
|
|
138
81
|
</h2>
|
|
139
|
-
<p class="
|
|
140
|
-
|
|
141
|
-
type-safe clients.
|
|
82
|
+
<p class="mt-2 text-sm leading-relaxed text-muted-foreground">
|
|
83
|
+
Define your models in <code class="font-mono text-xs">schema.prisma</code> and query them with types.
|
|
142
84
|
</p>
|
|
143
85
|
</a>
|
|
144
86
|
|
|
145
|
-
<a
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<h2
|
|
151
|
-
class="mb-3 text-xl md:text-2xl font-semibold font-brand group-hover:text-green-400 transition-colors"
|
|
152
|
-
>
|
|
87
|
+
<a class="group bg-background p-6 transition-colors hover:bg-muted/50"
|
|
88
|
+
href="https://caspian.tsnc.tech/docs/deploy"
|
|
89
|
+
target="_blank"
|
|
90
|
+
rel="noreferrer">
|
|
91
|
+
<h2 class="flex items-center gap-2 font-semibold">
|
|
153
92
|
Deploy
|
|
154
|
-
<span
|
|
155
|
-
class="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none"
|
|
156
|
-
>-></span
|
|
157
|
-
>
|
|
93
|
+
<span class="transition-transform group-hover:translate-x-1 motion-reduce:transform-none">→</span>
|
|
158
94
|
</h2>
|
|
159
|
-
<p class="
|
|
160
|
-
Instantly deploy your native Python app to Vercel, Railway, or Docker.
|
|
161
|
-
</p>
|
|
95
|
+
<p class="mt-2 text-sm leading-relaxed text-muted-foreground">Ship your Python app to Vercel, Railway or Docker.</p>
|
|
162
96
|
</a>
|
|
163
|
-
</
|
|
164
|
-
</
|
|
97
|
+
</nav>
|
|
98
|
+
</main>
|
|
165
99
|
""")
|
package/dist/src/app/layout.py
CHANGED
|
@@ -13,6 +13,21 @@ def layout():
|
|
|
13
13
|
<title>{{ metadata.title | default("Caspian App") }}</title>
|
|
14
14
|
<meta name="description"
|
|
15
15
|
content="{{ metadata.description | default('Powered by Caspian Framework') }}" />
|
|
16
|
+
{#
|
|
17
|
+
Everything a page put in `Metadata(extra={...})` -- robots, canonical,
|
|
18
|
+
Open Graph, Twitter cards. The page pipeline already merges `extra` into
|
|
19
|
+
this dict; without this loop it collected those values and dropped them,
|
|
20
|
+
so `extra` silently did nothing. Open Graph is keyed by `property`, every
|
|
21
|
+
other vocabulary by `name`.
|
|
22
|
+
#}
|
|
23
|
+
{% for meta_name, meta_content in (metadata | default({})).items()
|
|
24
|
+
if meta_name not in ("title", "description") and meta_content %}
|
|
25
|
+
{% if meta_name.startswith("og:") %}
|
|
26
|
+
<meta property="{{ meta_name }}" content="{{ meta_content }}" />
|
|
27
|
+
{% else %}
|
|
28
|
+
<meta name="{{ meta_name }}" content="{{ meta_content }}" />
|
|
29
|
+
{% endif %}
|
|
30
|
+
{% endfor %}
|
|
16
31
|
<link rel="icon"
|
|
17
32
|
href="/favicon.ico"
|
|
18
33
|
type="image/x-icon"
|
package/dist/tests/README.md
CHANGED
|
@@ -96,10 +96,124 @@ failure. Coverage is in `tests/test_browser_log.py`.
|
|
|
96
96
|
npm run check:fix
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
That runs `settings/fix.py
|
|
100
|
-
code, etc.)
|
|
101
|
-
(pyright) and failing tests (pytest)
|
|
102
|
-
reported `path:line:col`.
|
|
99
|
+
That runs `settings/fix.py`, which **formats first** (see below), then safely
|
|
100
|
+
fixes lint issues (dead imports, redundant code, etc.), then re-runs the full
|
|
101
|
+
gate so you see what's left. Type errors (pyright) and failing tests (pytest)
|
|
102
|
+
are never auto-fixed — fix those at the reported `path:line:col`.
|
|
103
|
+
|
|
104
|
+
## Formatting
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npm run format # write
|
|
108
|
+
npm run format:check # report only; exits 1 if work remains
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`settings/format.py` formats two surfaces, **markup first, then Python**:
|
|
112
|
+
|
|
113
|
+
1. **Markup** — the template inside every `html(r"""...""")`, via **djLint**.
|
|
114
|
+
2. **Python** — `main.py`, `src/**`, `settings/**`, `tests/**`, via
|
|
115
|
+
`ruff format`, then the `html(` rejoin described below.
|
|
116
|
+
|
|
117
|
+
The order is load-bearing. Reformatting a template changes how many lines its
|
|
118
|
+
string literal spans, which changes how ruff wraps the enclosing `html(...)`
|
|
119
|
+
call — so ruff must run last, or a single pass never converges.
|
|
120
|
+
|
|
121
|
+
### The `html(r"""` opening
|
|
122
|
+
|
|
123
|
+
The house style keeps the call and its template on one line, with the markup
|
|
124
|
+
starting on the next:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
return html(r"""
|
|
128
|
+
<div>…</div>
|
|
129
|
+
""", name=name)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`ruff format` will not produce that. Any call whose first argument is a
|
|
133
|
+
multiline string *and* which has other arguments gets exploded — in default and
|
|
134
|
+
preview style alike:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
return html(
|
|
138
|
+
r"""
|
|
139
|
+
<div>…</div>
|
|
140
|
+
""",
|
|
141
|
+
name=name,
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
So `format.py` rejoins the opening after ruff runs. The two steps feed each
|
|
146
|
+
other — rejoining the opening lets ruff also pull the closing `)` up on a
|
|
147
|
+
sole-argument call — so the pair is iterated to a fixed point. That is also why
|
|
148
|
+
`--check` replays the whole pipeline against a temporary mirror of the tree
|
|
149
|
+
instead of calling `ruff format --check`, which would flag every rejoined call
|
|
150
|
+
as unformatted.
|
|
151
|
+
|
|
152
|
+
**Running `ruff format` on its own (or on IDE save) will re-split these.**
|
|
153
|
+
`npm run format` puts them back.
|
|
154
|
+
|
|
155
|
+
### Why djLint, and why every block is proved
|
|
156
|
+
|
|
157
|
+
The markup layers four dialects in one string — HTML, Jinja `{{ }}`/`{% %}`,
|
|
158
|
+
PulsePoint `{ }`, and JS in `<script>` — and they nest:
|
|
159
|
+
|
|
160
|
+
```html
|
|
161
|
+
class="... {currentUrl === '{{ item['href'] }}' ? 'a' : 'b'}"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Prettier has no Jinja awareness: it de-indents `{% for %}` blocks to column 0
|
|
165
|
+
and joins `{% endfor %} {% endfor %}` onto one line. djLint is Jinja-aware and
|
|
166
|
+
does not reflow text, so PulsePoint expressions survive.
|
|
167
|
+
|
|
168
|
+
djLint is still a *general* HTML formatter, so it is never trusted. It will
|
|
169
|
+
insert a newline between a block tag and an adjacent inline or `<x-*>` tag,
|
|
170
|
+
which renders as a visible space — a custom element's `display` comes from CSS
|
|
171
|
+
the formatter cannot see. So each block is formatted and then checked against
|
|
172
|
+
`settings/_markup_equivalence.py`, a tokenizer that decides whether the result
|
|
173
|
+
is **guaranteed** to render identically. Only proven blocks are written; the
|
|
174
|
+
rest are skipped and printed with the reason:
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
skipped — djLint's output could not be proved to render identically:
|
|
178
|
+
src/app/crm/index.py
|
|
179
|
+
8: would trim whitespace around 'Only Active' inside <button>, whose
|
|
180
|
+
display is not known to be block-level
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**A skip is not a failure.** It means the reformat would have changed rendering,
|
|
184
|
+
so the block was left alone. Do not "fix" a skip by loosening the oracle.
|
|
185
|
+
|
|
186
|
+
`<x-*>` tags are registered with djLint (`--custom-html`), so a component tree
|
|
187
|
+
nests instead of sitting flat:
|
|
188
|
+
|
|
189
|
+
```html
|
|
190
|
+
<x-shell>
|
|
191
|
+
<x-brand />
|
|
192
|
+
<x-panel />
|
|
193
|
+
</x-shell>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
That does not weaken the check. The oracle still treats `<x-*>` as *inline*,
|
|
197
|
+
because a custom element's display comes from CSS — so indenting tags that were
|
|
198
|
+
already on separate lines is accepted (whitespace was there either way), while
|
|
199
|
+
separating two tags that were touching is still refused, since that inserts a
|
|
200
|
+
rendered space.
|
|
201
|
+
|
|
202
|
+
SVG elements (`<svg>`, `<path>`, `<g>`, …) count as block-level for a related
|
|
203
|
+
reason: an SVG fragment lays out no text, so indenting the children of an inline
|
|
204
|
+
`<svg>` in a component template cannot change what is drawn. `<text>`,
|
|
205
|
+
`<tspan>`, `<textPath>` and `<foreignObject>` are excluded — they do render
|
|
206
|
+
their content.
|
|
207
|
+
|
|
208
|
+
`<script>`, `<style>`, `<pre>`, and `<textarea>` bodies are masked out before
|
|
209
|
+
djLint runs, so code and preformatted text are preserved byte-for-byte by
|
|
210
|
+
construction rather than by proof — djLint otherwise reads `/>` inside a JS
|
|
211
|
+
regex as a tag delimiter and rewrites `.replace(/>/g, …)`.
|
|
212
|
+
|
|
213
|
+
Coverage is in `tests/test_format.py`. An oracle that wrongly returns *True* is
|
|
214
|
+
the only failure mode that silently corrupts pages, so the tests pin both
|
|
215
|
+
directions: transforms that must be recognised as safe, and transforms that must
|
|
216
|
+
never be.
|
|
103
217
|
|
|
104
218
|
### How unused-import (F401) removal stays safe
|
|
105
219
|
|
|
@@ -131,5 +245,6 @@ an `<x-*>` tag, so **the gate fails only on genuinely dead imports**. The
|
|
|
131
245
|
- **pyright** — type checker. Config in `[tool.pyright]`: `include = ["main.py", "src", "settings/*.py"]` with `exclude = [".venv", "node_modules", "**/__pycache__"]`, so it checks `main.py`, all of `src` (including the generated `src/lib/prisma/**` ORM), and the top-level `settings/*.py` tooling scripts (mirroring ruff's `include`). Pylance reads the same config, so the IDE and `npm run check` agree.
|
|
132
246
|
- **ruff** — linter. Config in `[tool.ruff]`; correctness-focused rules.
|
|
133
247
|
- **pytest** — test runner. Tests live in `tests/`.
|
|
248
|
+
- **djlint** — markup formatter, driven by `settings/format.py`. Invoked with explicit flags (`--profile jinja --indent 2 --max-line-length 120 --preserve-blank-lines`) rather than a `[tool.djlint]` config block, because blocks are formatted in a temp directory outside the project where that config would not be found.
|
|
134
249
|
|
|
135
250
|
Install/refresh them with `uv sync --group dev`.
|
package/dist/tests/conftest.py
CHANGED
|
@@ -82,6 +82,7 @@ def component_fixture_dir():
|
|
|
82
82
|
finally:
|
|
83
83
|
shutil.rmtree(scan_root, ignore_errors=True)
|
|
84
84
|
|
|
85
|
+
|
|
85
86
|
# Keep the app in its non-production code paths during tests (dev security
|
|
86
87
|
# headers, readable error messages, no HTTPS-only cookies).
|
|
87
88
|
os.environ.setdefault("APP_ENV", "development")
|
|
@@ -16,9 +16,7 @@ import main
|
|
|
16
16
|
async def _get(path: str) -> httpx2.Response:
|
|
17
17
|
# ASGITransport speaks to the app in-process; no server/socket required.
|
|
18
18
|
transport = httpx2.ASGITransport(app=main.app)
|
|
19
|
-
async with httpx2.AsyncClient(
|
|
20
|
-
transport=transport, base_url="http://testserver"
|
|
21
|
-
) as client:
|
|
19
|
+
async with httpx2.AsyncClient(transport=transport, base_url="http://testserver") as client:
|
|
22
20
|
return await client.get(path)
|
|
23
21
|
|
|
24
22
|
|
|
@@ -96,7 +96,7 @@ class TestDeferComponentRoots:
|
|
|
96
96
|
|
|
97
97
|
def test_component_root_is_wrapped_in_template(self):
|
|
98
98
|
# The transform operates on a full document body, as in production.
|
|
99
|
-
html =
|
|
99
|
+
html = '<body><div pp-component="abc"><span>x</span></div></body>'
|
|
100
100
|
out = main.defer_component_roots(html)
|
|
101
101
|
assert "<template" in out
|
|
102
102
|
assert 'pp-component="abc"' in out
|
|
@@ -106,8 +106,8 @@ class TestDeferComponentRoots:
|
|
|
106
106
|
'<body><div pp-component="abc" '
|
|
107
107
|
'title="{attr}" '
|
|
108
108
|
'class="probe {active}">'
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
"{text}"
|
|
110
|
+
"</div><p>{outside}</p></body>"
|
|
111
111
|
)
|
|
112
112
|
|
|
113
113
|
out = main.defer_component_roots(html)
|
|
@@ -118,10 +118,7 @@ class TestDeferComponentRoots:
|
|
|
118
118
|
assert "<p>{outside}</p>" in out
|
|
119
119
|
|
|
120
120
|
def test_finalize_html_defers_and_preserves_plain_scripts(self):
|
|
121
|
-
html = (
|
|
122
|
-
"<body><div pp-component=\"abc\">"
|
|
123
|
-
"<script>console.log(1)</script></div></body>"
|
|
124
|
-
)
|
|
121
|
+
html = '<body><div pp-component="abc"><script>console.log(1)</script></div></body>'
|
|
125
122
|
out = main.finalize_html(html)
|
|
126
123
|
assert "<script>console.log(1)</script>" in out
|
|
127
124
|
assert "<template" in out
|