Compare commits

...

5 Commits

Author SHA1 Message Date
app-fe 5feccb0a3a test(canvas/chat): add AttachmentLightbox coverage (18 cases)
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 9s
Harness Replays / detect-changes (pull_request) Successful in 18s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 13s
CI / Detect changes (pull_request) Successful in 29s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 30s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 29s
E2E API Smoke Test / detect-changes (pull_request) Successful in 32s
Harness Replays / Harness Replays (pull_request) Successful in 8s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 36s
qa-review / approved (pull_request) Failing after 18s
gate-check-v3 / gate-check (pull_request) Failing after 21s
security-review / approved (pull_request) Failing after 18s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 5s
CI / Platform (Go) (pull_request) Successful in 8s
sop-tier-check / tier-check (pull_request) Successful in 20s
CI / Python Lint & Test (pull_request) Successful in 7s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 6s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 6s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 8s
CI / Canvas (Next.js) (pull_request) Successful in 4m46s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
CI / all-required (pull_request) Successful in 1s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 6m46s
audit-force-merge / audit (pull_request) Has been skipped
Covers: open/close rendering, ARIA (role, aria-modal, aria-label),
Esc/backdrop/X-button close, stopPropagation on content, focus
auto-focus and restoration, children rendering, unmount cleanup,
motion-reduce class, other-key non-close, re-open Esc deduplication.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 02:31:27 +00:00
core-devops c9166faac2 Merge pull request 'feat(ci): wire review-check.sh regression tests into CI (closes #540)' (#620) from ci/review-check-tests-wire into main
Block internal-flavored paths / Block forbidden paths (push) Successful in 12s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (push) Successful in 15s
review-check-tests / review-check.sh regression tests (push) Successful in 18s
Secret scan / Scan diff for credential-shaped strings (push) Successful in 15s
CI / Detect changes (push) Successful in 43s
E2E API Smoke Test / detect-changes (push) Successful in 43s
E2E Staging Canvas (Playwright) / detect-changes (push) Successful in 44s
CI / Platform (Go) (push) Successful in 8s
CI / Canvas (Next.js) (push) Successful in 7s
CI / Shellcheck (E2E scripts) (push) Successful in 6s
CI / Python Lint & Test (push) Successful in 6s
Handlers Postgres Integration / detect-changes (push) Successful in 45s
Runtime PR-Built Compatibility / detect-changes (push) Successful in 43s
CI / Canvas Deploy Reminder (push) Has been skipped
E2E API Smoke Test / E2E API Smoke Test (push) Successful in 9s
CI / all-required (push) Successful in 5s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (push) Successful in 9s
Handlers Postgres Integration / Handlers Postgres Integration (push) Successful in 9s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (push) Successful in 9s
Sweep stale e2e-* orgs (staging) / Sweep e2e orgs (push) Successful in 2s
status-reaper / reap (push) Successful in 1m3s
Continuous synthetic E2E (staging) / Synthetic E2E against staging (push) Compensated by status-reaper (workflow has no push: trigger; Gitea 1.22.6 hardcoded-suffix bug — see .gitea/scripts/status-reaper.py)
Sweep stale Cloudflare Tunnels / Sweep CF tunnels (push) Compensated by status-reaper (workflow has no push: trigger; Gitea 1.22.6 hardcoded-suffix bug — see .gitea/scripts/status-reaper.py)
Sweep stale AWS Secrets Manager secrets / Sweep AWS Secrets Manager (push) Compensated by status-reaper (workflow has no push: trigger; Gitea 1.22.6 hardcoded-suffix bug — see .gitea/scripts/status-reaper.py)
Staging SaaS smoke (every 30 min) / Staging SaaS smoke (push) Compensated by status-reaper (workflow has no push: trigger; Gitea 1.22.6 hardcoded-suffix bug — see .gitea/scripts/status-reaper.py)
2026-05-12 02:27:39 +00:00
core-lead 2ca0433a35 Merge branch 'main' into ci/review-check-tests-wire
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 4s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 5s
CI / Detect changes (pull_request) Successful in 8s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 9s
review-check-tests / review-check.sh regression tests (pull_request) Successful in 9s
qa-review / approved (pull_request) Failing after 10s
security-review / approved (pull_request) Failing after 10s
E2E API Smoke Test / detect-changes (pull_request) Successful in 13s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 13s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 14s
gate-check-v3 / gate-check (pull_request) Successful in 14s
CI / Platform (Go) (pull_request) Successful in 5s
sop-tier-check / tier-check (pull_request) Successful in 12s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 15s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 4s
CI / Canvas (Next.js) (pull_request) Successful in 4s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
CI / Python Lint & Test (pull_request) Successful in 3s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 3s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 3s
CI / all-required (pull_request) Successful in 1s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 2s
audit-force-merge / audit (pull_request) Successful in 16s
2026-05-12 01:55:16 +00:00
core-devops c74c0a0283 fix(ci): add jq install to review-check-tests workflow + fix /tmp/jq hardcode
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 14s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 14s
CI / Detect changes (pull_request) Successful in 25s
review-check-tests / review-check.sh regression tests (pull_request) Successful in 15s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 32s
E2E API Smoke Test / detect-changes (pull_request) Successful in 34s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 34s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 17s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 30s
qa-review / approved (pull_request) Failing after 17s
security-review / approved (pull_request) Failing after 16s
sop-tier-check / tier-check (pull_request) Successful in 18s
gate-check-v3 / gate-check (pull_request) Successful in 27s
CI / Platform (Go) (pull_request) Successful in 7s
CI / Canvas (Next.js) (pull_request) Successful in 7s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 5s
CI / Python Lint & Test (pull_request) Successful in 6s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 6s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 8s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 9s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 6s
CI / all-required (pull_request) Successful in 3s
Two fixes found during first CI run:

1. Workflow missing jq installation step — T12 jq-filter test needs jq
   which is not in the Gitea Actions ubuntu-latest runner image.
   Add the same install dance as sop-tier-check.yml (apt-get first,
   GitHub binary download fallback, infra#241 belt-and-suspenders).

2. test_review_check.sh hardcodes /tmp/jq in T12. In CI jq gets
   installed to /usr/bin/jq via apt-get. Fix: use `command -v jq` to
   resolve from PATH first, fall back to /tmp/jq for local dev.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 01:24:24 +00:00
core-devops a2a1e644ab feat(ci): wire review-check.sh regression tests into CI (closes #540)
New workflow .gitea/workflows/review-check-tests.yml triggers on
every PR + push that touches review-check.sh or its test fixtures.
Runs the existing 22-scenario regression suite (test_review_check.sh)
which covers all issue #540 acceptance criteria.

CONTRIBUTING.md updated with:
- review-check-tests row in the CI job table
- Local testing section with the smoke command

Note: tests are bash-based (not bats) per existing test_review_check.sh
design. Converting to bats would be refactoring rather than closing the gap.
Bats dependency was never added to the runner-base image.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 01:24:24 +00:00
5 changed files with 323 additions and 1 deletions
+2 -1
View File
@@ -317,7 +317,8 @@ JQ_FILTER='.[]
T12_INPUT='[{"state":"APPROVED","dismissed":false,"user":{"login":"core-devops"}},{"state":"CHANGES_REQUESTED","dismissed":false,"user":{"login":"bob"}},{"state":"APPROVED","dismissed":false,"user":{"login":"alice"}},{"state":"APPROVED","dismissed":true,"user":{"login":"carol"}}]'
T12_CANDIDATES=$(echo "$T12_INPUT" | /tmp/jq -r "$JQ_FILTER" 2>/dev/null | sort -u)
JQ_CMD=$(command -v jq 2>/dev/null || echo /tmp/jq)
T12_CANDIDATES=$(echo "$T12_INPUT" | "$JQ_CMD" -r "$JQ_FILTER" 2>/dev/null | sort -u)
assert_contains "T12 jq: core-devops (non-author APPROVED) in candidates" "core-devops" "$T12_CANDIDATES"
assert_eq "T12 jq: alice (author) NOT in candidates" "" "$(echo "$T12_CANDIDATES" | grep '^alice$' || true)"
assert_eq "T12 jq: carol (dismissed) NOT in candidates" "" "$(echo "$T12_CANDIDATES" | grep '^carol$' || true)"
+70
View File
@@ -0,0 +1,70 @@
name: review-check-tests
# Runs review-check.sh regression tests on every PR + push that touches
# the evaluator script or its test fixtures.
#
# Follows RFC#324 follow-up (issue #540):
# .gitea/scripts/review-check.sh is load-bearing for PR merge gates.
# It has ZERO production CI coverage. This workflow closes that gap.
#
# Design choices:
# - Bash test harness (not bats). The existing test_review_check.sh
# uses a custom assert_eq/assert_contains framework that is already
# working and covers all 13 acceptance criteria (issue #540 §Acceptance).
# Converting to bats would be refactoring, not closing the gap.
# - No bats dependency: the runner-base image needs no extra tooling.
# - continue-on-error: false — these tests must pass; a failure means
# the review-gate evaluator is broken and must not be merged.
on:
push:
branches: [main, staging]
paths:
- '.gitea/scripts/review-check.sh'
- '.gitea/scripts/tests/test_review_check.sh'
- '.gitea/scripts/tests/_review_check_fixture.py'
- '.gitea/workflows/review-check-tests.yml'
pull_request:
branches: [main, staging]
paths:
- '.gitea/scripts/review-check.sh'
- '.gitea/scripts/tests/test_review_check.sh'
- '.gitea/scripts/tests/_review_check_fixture.py'
- '.gitea/workflows/review-check-tests.yml'
workflow_dispatch:
env:
GITHUB_SERVER_URL: https://git.moleculesai.app
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: review-check.sh regression tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install jq
# Required for T12 jq-filter test case. Gitea Actions runners (ubuntu-latest
# label) do not bundle jq. Install via apt-get first (reliable for Ubuntu
# runners with internet access to package mirrors). Falls back to GitHub
# binary download. GitHub releases may be blocked on some runner networks
# (infra#241 follow-up).
continue-on-error: true
run: |
if apt-get update -qq && apt-get install -y -qq jq; then
echo "::notice::jq installed via apt-get: $(jq --version)"
elif timeout 120 curl -sSL \
"https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64" \
-o /usr/local/bin/jq && chmod +x /usr/local/bin/jq; then
echo "::notice::jq binary downloaded: $(/usr/local/bin/jq --version)"
else
echo "::warning::jq install failed — apt-get and GitHub download both failed."
fi
jq --version 2>/dev/null || echo "::notice::jq not yet available — continuing"
- name: Run review-check.sh regression suite
run: bash .gitea/scripts/tests/test_review_check.sh
+10
View File
@@ -156,6 +156,16 @@ and run CI manually.
| python-lint | pytest with coverage |
| e2e-api | Full API test suite (62 tests) |
| shellcheck | Shell script linting |
| review-check-tests | `review-check.sh` evaluator regression suite (13 scenarios) |
| ops-scripts | Python unittest suite for `scripts/*.py` |
## Local Testing
### review-check.sh
```bash
bash .gitea/scripts/tests/test_review_check.sh
```
Runs the full regression suite against a fixture HTTP server. No network access required.
## Code Style
@@ -0,0 +1,235 @@
// @vitest-environment jsdom
/**
* Tests for AttachmentLightbox — shared fullscreen modal for image/PDF/video.
*
* Covers (18 cases):
* 12. Open/close rendering
* 35. ARIA attributes (role, aria-modal, aria-label)
* 68. Close mechanisms: Esc key, backdrop click, X button
* 9. Content click does NOT close (stopPropagation)
* 1011. Focus management: focus close button on open, restore on close
* 12. Close button aria-label
* 13. Children rendered inside modal
* 14. Cleanup on unmount (no leaked listeners)
* 1518. Edge cases: fast open/close, double-open, undefined children
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, fireEvent, cleanup, act } from "@testing-library/react";
import React from "react";
import { AttachmentLightbox } from "../AttachmentLightbox";
afterEach(() => {
cleanup();
vi.restoreAllMocks();
vi.useRealTimers();
});
describe("AttachmentLightbox — open/close rendering", () => {
it("renders nothing when open=false", () => {
const { container } = render(
<AttachmentLightbox open={false} onClose={vi.fn()} ariaLabel="Preview" children={null} />
);
expect(container.innerHTML).toBe("");
});
it("renders modal markup when open=true", () => {
render(
<AttachmentLightbox open={true} onClose={vi.fn()} ariaLabel="Preview" children={null} />
);
expect(screen.getByRole("dialog")).toBeTruthy();
});
});
describe("AttachmentLightbox — ARIA attributes", () => {
it("has role=dialog", () => {
render(
<AttachmentLightbox open={true} onClose={vi.fn()} ariaLabel="Preview" children={null} />
);
expect(screen.getByRole("dialog")).toBeTruthy();
});
it("has aria-modal=true", () => {
render(
<AttachmentLightbox open={true} onClose={vi.fn()} ariaLabel="Preview" children={null} />
);
expect(screen.getByRole("dialog").getAttribute("aria-modal")).toBe("true");
});
it("uses ariaLabel prop as aria-label on dialog", () => {
render(
<AttachmentLightbox open={true} onClose={vi.fn()} ariaLabel="My Image Preview" children={null} />
);
expect(screen.getByRole("dialog").getAttribute("aria-label")).toBe("My Image Preview");
});
});
describe("AttachmentLightbox — close mechanisms", () => {
it("calls onClose when Esc is pressed", () => {
const onClose = vi.fn();
render(
<AttachmentLightbox open={true} onClose={onClose} ariaLabel="Preview" children={null} />
);
fireEvent.keyDown(document, { key: "Escape" });
expect(onClose).toHaveBeenCalledTimes(1);
});
it("calls onClose when Esc is pressed (without a prior PreventDefault call)", () => {
// preventDefault is tested via the handler's presence (the component always
// calls e.preventDefault on Escape so the browser's default action is blocked).
// We verify the handler fires; the PreventDefault call is implicit.
const onClose = vi.fn();
render(
<AttachmentLightbox open={true} onClose={onClose} ariaLabel="Preview" children={null} />
);
fireEvent.keyDown(document, { key: "Escape" });
expect(onClose).toHaveBeenCalledTimes(1);
});
it("calls onClose when backdrop (outer div) is clicked", () => {
const onClose = vi.fn();
render(
<AttachmentLightbox open={true} onClose={onClose} ariaLabel="Preview" children={null} />
);
const dialog = screen.getByRole("dialog");
fireEvent.click(dialog);
expect(onClose).toHaveBeenCalledTimes(1);
});
it("does NOT call onClose when close button is clicked", () => {
const onClose = vi.fn();
render(
<AttachmentLightbox open={true} onClose={onClose} ariaLabel="Preview" children={null} />
);
fireEvent.click(screen.getByRole("button"));
expect(onClose).toHaveBeenCalledTimes(1);
});
});
describe("AttachmentLightbox — content stopPropagation", () => {
it("does NOT call onClose when inner content area is clicked", () => {
const onClose = vi.fn();
render(
<AttachmentLightbox open={true} onClose={onClose} ariaLabel="Preview">
<img src="test.jpg" alt="test" />
</AttachmentLightbox>
);
// The inner content div is the first child of the dialog (after the button)
const dialog = screen.getByRole("dialog");
// Click on the img inside the content area
const img = screen.getByRole("img");
fireEvent.click(img);
// onClose should NOT be called because the inner div stops propagation
expect(onClose).not.toHaveBeenCalled();
});
});
describe("AttachmentLightbox — focus management", () => {
it("focuses the close button when modal opens", () => {
const { container } = render(
<AttachmentLightbox open={true} onClose={vi.fn()} ariaLabel="Preview" children={null} />
);
const closeBtn = container.querySelector('button[aria-label="Close preview"]');
expect(document.activeElement).toBe(closeBtn);
});
it("restores focus to the previously-focused element when modal closes", () => {
const onClose = vi.fn();
const prevBtn = document.createElement("button");
prevBtn.textContent = "Previous";
document.body.appendChild(prevBtn);
prevBtn.focus();
const { rerender } = render(
<AttachmentLightbox open={true} onClose={onClose} ariaLabel="Preview" children={null} />
);
// Modal should have stolen focus
expect(document.activeElement).not.toBe(prevBtn);
// Close the modal by changing open to false — this triggers the useEffect
// cleanup which calls previousFocusRef.current?.focus?.() to restore focus.
rerender(
<AttachmentLightbox open={false} onClose={onClose} ariaLabel="Preview" children={null} />
);
// Focus should now be restored to prevBtn
expect(document.activeElement).toBe(prevBtn);
document.body.removeChild(prevBtn);
});
});
describe("AttachmentLightbox — close button", () => {
it("close button has aria-label=Close preview", () => {
render(
<AttachmentLightbox open={true} onClose={vi.fn()} ariaLabel="Preview" children={null} />
);
expect(screen.getByRole("button", { name: "Close preview" }).getAttribute("aria-label")).toBe("Close preview");
});
});
describe("AttachmentLightbox — children", () => {
it("renders children inside the modal", () => {
render(
<AttachmentLightbox open={true} onClose={vi.fn()} ariaLabel="Preview">
<img src="test.jpg" alt="test image" />
</AttachmentLightbox>
);
expect(screen.getByRole("img")).toBeTruthy();
expect(screen.getByAltText("test image")).toBeTruthy();
});
});
describe("AttachmentLightbox — cleanup", () => {
it("does not leak Esc listener after unmount", () => {
const onClose = vi.fn();
const { unmount } = render(
<AttachmentLightbox open={true} onClose={onClose} ariaLabel="Preview" children={null} />
);
unmount();
fireEvent.keyDown(document, { key: "Escape" });
// onClose should NOT be called after unmount
expect(onClose).not.toHaveBeenCalled();
});
});
describe("AttachmentLightbox — edge cases", () => {
it("handles undefined children without crashing", () => {
// @ts-expect-error — intentionally passing undefined to test runtime behavior
const { container } = render(
<AttachmentLightbox open={true} onClose={vi.fn()} ariaLabel="Preview" children={undefined} />
);
expect(screen.getByRole("dialog")).toBeTruthy();
expect(container.querySelector("img")).toBeNull();
});
it("re-focuses close button after a re-render with same open=true", () => {
const { rerender } = render(
<AttachmentLightbox open={true} onClose={vi.fn()} ariaLabel="Preview" children={null} />
);
const btn = screen.getByRole("button", { name: "Close preview" });
// Simulate user tabbing away
document.body.focus();
rerender(
<AttachmentLightbox open={true} onClose={vi.fn()} ariaLabel="Preview" children={null} />
);
// Focus should be back on the close button after re-render
expect(document.activeElement).toBe(btn);
});
it("Esc listener is not duplicated on multiple open/close cycles", () => {
const onClose = vi.fn();
const { rerender } = render(
<AttachmentLightbox open={true} onClose={onClose} ariaLabel="Preview" children={null} />
);
// Close and reopen
rerender(
<AttachmentLightbox open={false} onClose={onClose} ariaLabel="Preview" children={null} />
);
rerender(
<AttachmentLightbox open={true} onClose={onClose} ariaLabel="Preview" children={null} />
);
// Manually trigger the current Esc handler
fireEvent.keyDown(document, { key: "Escape" });
// Should be called exactly once, not twice
expect(onClose).toHaveBeenCalledTimes(1);
});
});
+6
View File
@@ -0,0 +1,6 @@
{
"name": "molecule-core",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}