feat: capture shared mobile content (#223)
This commit is contained in:
1 parent
bca27f0330
commit
f655a5115a
9 files changed
+313
-3
No files matched your search
@@ -534,6 +534,20 @@ async def test_new_issue_sheet_exposes_touch_safe_release_planning_controls():
|
||||
assert "dueDate: qs('#create-issue-due-date').value" in html
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_dashboard_launches_share_capture_with_draft_conflict_choices():
|
||||
html = await dashboard()
|
||||
|
||||
assert '<link rel="manifest" href="manifest.webmanifest"' in html
|
||||
assert "issueCapture.stageSharedContent(sharedLaunch)" in html
|
||||
assert 'id="use-shared-content"' in html
|
||||
assert 'id="resume-issue-draft"' in html
|
||||
assert 'id="shared-content-conflict"' in html
|
||||
assert '.shared-content-actions button { min-height:44px;' in html
|
||||
assert "history.replaceState({}, '', cleanUrl)" in html
|
||||
assert "navigator.serviceWorker.register('/service-worker.js')" in html
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mobile_my_work_exposes_touch_safe_milestone_lane_and_issue_editor():
|
||||
html = await dashboard()
|
||||
@@ -1232,6 +1246,105 @@ first.submit(draft).catch(() => {{
|
||||
assert output["number"] == 17
|
||||
|
||||
|
||||
def test_issue_capture_normalizes_shared_mobile_content_without_repeating_source_url():
|
||||
script = f"""
|
||||
const createIssueCapture = require({json.dumps(str(CREATE_ISSUE_SHEET))});
|
||||
const normalized = createIssueCapture.normalizeSharedContent({{
|
||||
title: ' Production alert ',
|
||||
text: 'Latency crossed the threshold https://status.example/incidents/42',
|
||||
url: 'https://status.example/incidents/42',
|
||||
}});
|
||||
process.stdout.write(JSON.stringify(normalized));
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
)
|
||||
|
||||
assert json.loads(result.stdout) == {
|
||||
"title": "Production alert",
|
||||
"body": "Latency crossed the threshold https://status.example/incidents/42",
|
||||
}
|
||||
|
||||
|
||||
def test_issue_capture_suggests_a_title_when_mobile_share_only_contains_text():
|
||||
script = f"""
|
||||
const createIssueCapture = require({json.dumps(str(CREATE_ISSUE_SHEET))});
|
||||
process.stdout.write(JSON.stringify(createIssueCapture.normalizeSharedContent({{
|
||||
text: 'Investigate checkout latency before the release window opens. More diagnostic context follows.'
|
||||
}})));
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
)
|
||||
|
||||
assert json.loads(result.stdout) == {
|
||||
"title": "Investigate checkout latency before the release window opens.",
|
||||
"body": "Investigate checkout latency before the release window opens. More diagnostic context follows.",
|
||||
}
|
||||
|
||||
|
||||
def test_issue_capture_keeps_existing_draft_until_shared_content_is_accepted():
|
||||
script = f"""
|
||||
const createIssueCapture = require({json.dumps(str(CREATE_ISSUE_SHEET))});
|
||||
const values = new Map();
|
||||
const storage = {{
|
||||
getItem:key => values.get(key) || null,
|
||||
setItem:(key,value) => values.set(key,value),
|
||||
removeItem:key => values.delete(key),
|
||||
}};
|
||||
let capture = createIssueCapture({{storage, fetchJson:()=>Promise.resolve({{}})}});
|
||||
capture.saveDraft({{
|
||||
repository:'stackchain/api', title:'Existing draft', body:'Keep me', labelIds:[3], milestoneId:9,
|
||||
}});
|
||||
const staged = capture.stageSharedContent({{title:'Shared alert', text:'Investigate', url:'https://status.example/42'}});
|
||||
const before = capture.loadDraft();
|
||||
capture = createIssueCapture({{storage, fetchJson:()=>Promise.resolve({{}})}});
|
||||
const pendingAfterReload = capture.pendingSharedContent();
|
||||
const accepted = capture.acceptSharedContent();
|
||||
process.stdout.write(JSON.stringify({{staged, before, pendingAfterReload, accepted, pending:capture.pendingSharedContent()}}));
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
)
|
||||
|
||||
assert json.loads(result.stdout) == {
|
||||
"staged": {"status": "conflict"},
|
||||
"before": {
|
||||
"repository": "stackchain/api", "title": "Existing draft", "body": "Keep me",
|
||||
"labelIds": [3], "milestoneId": 9,
|
||||
},
|
||||
"pendingAfterReload": {
|
||||
"title": "Shared alert", "body": "Investigate\n\nhttps://status.example/42",
|
||||
},
|
||||
"accepted": {
|
||||
"repository": "stackchain/api", "title": "Shared alert",
|
||||
"body": "Investigate\n\nhttps://status.example/42", "labelIds": [3], "milestoneId": 9,
|
||||
},
|
||||
"pending": None,
|
||||
}
|
||||
|
||||
|
||||
def test_issue_capture_can_resume_existing_draft_and_discard_staged_share():
|
||||
script = f"""
|
||||
const createIssueCapture = require({json.dumps(str(CREATE_ISSUE_SHEET))});
|
||||
const values = new Map();
|
||||
const storage = {{getItem:key=>values.get(key)||null, setItem:(key,value)=>values.set(key,value), removeItem:key=>values.delete(key)}};
|
||||
const capture = createIssueCapture({{storage, fetchJson:()=>Promise.resolve({{}})}});
|
||||
capture.saveDraft({{repository:'stackchain/api', title:'Existing', body:'Keep', labelIds:[]}});
|
||||
capture.stageSharedContent({{title:'Incoming', text:'Replace'}});
|
||||
capture.discardSharedContent();
|
||||
process.stdout.write(JSON.stringify({{draft:capture.loadDraft(), pending:capture.pendingSharedContent()}}));
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
)
|
||||
|
||||
assert json.loads(result.stdout) == {
|
||||
"draft": {"repository": "stackchain/api", "title": "Existing", "body": "Keep", "labelIds": []},
|
||||
"pending": None,
|
||||
}
|
||||
|
||||
|
||||
def test_issue_capture_loads_repository_labels_with_priorities_first():
|
||||
script = f"""
|
||||
const createIssueCapture = require({json.dumps(str(CREATE_ISSUE_SHEET))});
|
||||
|
||||
@@ -61,4 +61,24 @@ async def test_work_page_endpoint_rejects_unknown_stream_before_upstream_io(monk
|
||||
response = await client.get("/api/v1/work/unknown?page=2")
|
||||
|
||||
assert response.status_code == 422
|
||||
assert called is False
|
||||
assert called is False
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_pwa_assets_expose_root_scoped_share_target_without_caching_api_data():
|
||||
transport = httpx.ASGITransport(app=main.app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
manifest = await client.get("/manifest.webmanifest")
|
||||
worker = await client.get("/service-worker.js")
|
||||
|
||||
assert manifest.status_code == 200
|
||||
assert manifest.headers["content-type"].startswith("application/manifest+json")
|
||||
assert manifest.json()["share_target"] == {
|
||||
"action": "/", "method": "GET", "enctype": "application/x-www-form-urlencoded",
|
||||
"params": {"title": "title", "text": "text", "url": "url"},
|
||||
}
|
||||
assert worker.status_code == 200
|
||||
assert worker.headers["content-type"].startswith("application/javascript")
|
||||
assert worker.headers["service-worker-allowed"] == "/"
|
||||
assert "request.url.includes('/api/')" in worker.text
|
||||
assert "request.method !== 'GET'" in worker.text
|
||||
Reference in new issue
Block a user