test(examples): guard browser e2e product path
req: examples/005 req: convention/002
This commit is contained in:
@@ -27,7 +27,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>Impact <input name="impact" type="number" min="1" max="9" value="7"></label>
|
<label>Impact <input name="impact" type="number" min="1" max="9" value="7"></label>
|
||||||
<button class="primary-action" type="button" form="launch-work" data-slhx-handle="launch_work" data-slhx-on="click">Create issue</button>
|
<button class="primary-action" type="button" form="launch-work" data-slhx-handle="launch_work">Create issue</button>
|
||||||
</form>
|
</form>
|
||||||
<div class="quick-actions">
|
<div class="quick-actions">
|
||||||
<button type="button" data-slhx-handle="simulate_push">Simulate server push</button>
|
<button type="button" data-slhx-handle="simulate_push">Simulate server push</button>
|
||||||
|
|||||||
@@ -23,10 +23,7 @@
|
|||||||
function formOwner(el) {
|
function formOwner(el) {
|
||||||
if (!el || el.tagName === "FORM") return el;
|
if (!el || el.tagName === "FORM") return el;
|
||||||
if (el.getAttribute && el.getAttribute("form")) return document.getElementById(el.getAttribute("form"));
|
if (el.getAttribute && el.getAttribute("form")) return document.getElementById(el.getAttribute("form"));
|
||||||
const closest = el.closest ? el.closest("form") : null;
|
return el.closest ? el.closest("form") : null;
|
||||||
if (closest) return closest;
|
|
||||||
const root = rootOf(el);
|
|
||||||
return root && root.querySelector("form[data-slhx-form]");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formHandleId(form) {
|
function formHandleId(form) {
|
||||||
|
|||||||
@@ -12,14 +12,28 @@ fn canonical_examples_do_not_author_browser_javascript() {
|
|||||||
}
|
}
|
||||||
for (line_no, line) in text.lines().enumerate() {
|
for (line_no, line) in text.lines().enumerate() {
|
||||||
let trimmed = line.trim();
|
let trimmed = line.trim();
|
||||||
if trimmed.contains("<script") && !trimmed.contains(r#"<script src="/slhx.js" defer></script>"#) {
|
if trimmed.contains("<script")
|
||||||
failures.push(format!("{}:{}: inline <script> is not allowed", path.display(), line_no + 1));
|
&& !trimmed.contains(r#"<script src="/slhx.js" defer></script>"#)
|
||||||
|
{
|
||||||
|
failures.push(format!(
|
||||||
|
"{}:{}: inline <script> is not allowed",
|
||||||
|
path.display(),
|
||||||
|
line_no + 1
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if contains_inline_event_handler(trimmed) {
|
if contains_inline_event_handler(trimmed) {
|
||||||
failures.push(format!("{}:{}: inline on*= handler is not allowed", path.display(), line_no + 1));
|
failures.push(format!(
|
||||||
|
"{}:{}: inline on*= handler is not allowed",
|
||||||
|
path.display(),
|
||||||
|
line_no + 1
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if trimmed.to_ascii_lowercase().contains("javascript:") {
|
if trimmed.to_ascii_lowercase().contains("javascript:") {
|
||||||
failures.push(format!("{}:{}: javascript: URL is not allowed", path.display(), line_no + 1));
|
failures.push(format!(
|
||||||
|
"{}:{}: javascript: URL is not allowed",
|
||||||
|
path.display(),
|
||||||
|
line_no + 1
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -27,6 +41,23 @@ fn canonical_examples_do_not_author_browser_javascript() {
|
|||||||
assert!(failures.is_empty(), "{}", failures.join("\n"));
|
assert!(failures.is_empty(), "{}", failures.join("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn browser_e2e_does_not_shortcut_product_interactions() {
|
||||||
|
// req: examples/005
|
||||||
|
let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
|
||||||
|
let browser_e2e = root.join("examples/techdemo/tests/browser_e2e.rs");
|
||||||
|
let source = std::fs::read_to_string(browser_e2e).unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!source.contains("fetch(\"/\""),
|
||||||
|
"browser E2E must drive UI, not post directly"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!source.contains("applyBatch(buffer"),
|
||||||
|
"browser E2E must not apply wire batches manually"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn scan_examples(dir: &Path, visit: &mut impl FnMut(&Path, &str)) {
|
fn scan_examples(dir: &Path, visit: &mut impl FnMut(&Path, &str)) {
|
||||||
for entry in std::fs::read_dir(dir).unwrap() {
|
for entry in std::fs::read_dir(dir).unwrap() {
|
||||||
let entry = entry.unwrap();
|
let entry = entry.unwrap();
|
||||||
@@ -44,7 +75,10 @@ fn scan_examples(dir: &Path, visit: &mut impl FnMut(&Path, &str)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_example_source(path: &PathBuf) -> bool {
|
fn is_example_source(path: &PathBuf) -> bool {
|
||||||
matches!(path.extension().and_then(|ext| ext.to_str()), Some("rs" | "heml" | "html"))
|
matches!(
|
||||||
|
path.extension().and_then(|ext| ext.to_str()),
|
||||||
|
Some("rs" | "heml" | "html")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contains_inline_event_handler(line: &str) -> bool {
|
fn contains_inline_event_handler(line: &str) -> bool {
|
||||||
@@ -52,7 +86,11 @@ fn contains_inline_event_handler(line: &str) -> bool {
|
|||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while i + 3 < bytes.len() {
|
while i + 3 < bytes.len() {
|
||||||
let boundary = bytes[i].is_ascii_whitespace() || bytes[i] == b'<';
|
let boundary = bytes[i].is_ascii_whitespace() || bytes[i] == b'<';
|
||||||
if boundary && bytes[i + 1] == b'o' && bytes[i + 2] == b'n' && bytes[i + 3].is_ascii_lowercase() {
|
if boundary
|
||||||
|
&& bytes[i + 1] == b'o'
|
||||||
|
&& bytes[i + 2] == b'n'
|
||||||
|
&& bytes[i + 3].is_ascii_lowercase()
|
||||||
|
{
|
||||||
let mut j = i + 4;
|
let mut j = i + 4;
|
||||||
while j < bytes.len() && bytes[j].is_ascii_lowercase() {
|
while j < bytes.len() && bytes[j].is_ascii_lowercase() {
|
||||||
j += 1;
|
j += 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user