feat: launch mobile work from PWA shortcuts (Closes #737)
This commit is contained in:
1 parent
e7d5196987
commit
bb1712279c
10 files changed
+135
No files matched your search
@@ -876,6 +876,21 @@ async def test_anonymous_share_target_redirect_preserves_only_bounded_capture_fi
|
||||
assert response.headers["cache-control"] == "no-store"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_anonymous_app_shortcut_preserves_only_a_known_launch_action(access_control):
|
||||
transport = httpx.ASGITransport(app=main.app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url="https://test") as client:
|
||||
valid = await client.get("/", params={"launch": "agenda", "next": "https://evil.example"})
|
||||
invalid = await client.get("/", params={"launch": "delete-account"})
|
||||
|
||||
assert valid.status_code == 303
|
||||
assert parse_qs(urlsplit(valid.headers["location"]).query) == {
|
||||
"continue": ["./?launch=agenda"]
|
||||
}
|
||||
assert invalid.status_code == 303
|
||||
assert invalid.headers["location"] == "login"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_oversized_share_target_is_not_carried_through_login(access_control):
|
||||
transport = httpx.ASGITransport(app=main.app)
|
||||
|
||||
@@ -8,6 +8,33 @@ from tests.dashboard_bundle import dashboard
|
||||
|
||||
|
||||
MOBILE_LAUNCH = Path(__file__).resolve().parents[1] / "frontend" / "mobile-launch.js"
|
||||
APP_SHORTCUTS = Path(__file__).resolve().parents[1] / "frontend" / "mobile-app-shortcuts.js"
|
||||
|
||||
|
||||
def test_installed_app_shortcut_accepts_only_bounded_actions_and_runs_once():
|
||||
script = f"""
|
||||
const shortcuts = require({json.dumps(str(APP_SHORTCUTS))});
|
||||
const calls = [];
|
||||
const controller = shortcuts.createController({{
|
||||
search:'?launch=continue',
|
||||
continueWork:() => calls.push('continue'),
|
||||
newIssue:() => calls.push('new'),
|
||||
agenda:() => calls.push('agenda'),
|
||||
}});
|
||||
Promise.resolve(controller.run()).then(() => controller.run()).then(() => {{
|
||||
process.stdout.write(JSON.stringify({{
|
||||
action:controller.action(), calls,
|
||||
invalid:shortcuts.parse('?launch=delete-account'),
|
||||
shared:shortcuts.parse('?launch=new&title=Shared'),
|
||||
}}));
|
||||
}});
|
||||
"""
|
||||
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == {
|
||||
"action": "continue", "calls": ["continue"], "invalid": None, "shared": "new"
|
||||
}
|
||||
|
||||
|
||||
def test_mobile_launch_selects_priority_queue_without_overriding_saved_choice():
|
||||
@@ -63,8 +90,13 @@ async def test_mobile_launch_progressively_discloses_secondary_controls():
|
||||
assert 'id="work-settings-toggle"' in html
|
||||
assert 'id="active-work-queue"' in html
|
||||
assert '<script src="static/mobile-launch.js"></script>' in html
|
||||
assert '<script src="static/mobile-app-shortcuts.js"></script>' in html
|
||||
service_worker = (Path(__file__).resolve().parents[1] / "frontend" / "service-worker.js").read_text()
|
||||
assert "BASE + 'static/mobile-launch.js'" in service_worker
|
||||
assert "BASE + 'static/mobile-app-shortcuts.js'" in service_worker
|
||||
bootstrap = (Path(__file__).resolve().parents[1] / "frontend" / "dashboard.js").read_text()
|
||||
assert "mobileAppShortcuts.createController" in bootstrap
|
||||
assert bootstrap.index("await load();\n await appShortcut.run();") < bootstrap.index("contextPoller.start();")
|
||||
assert "mobileLaunch.chooseFilter" in html
|
||||
assert "agenda: counts.agenda" in html
|
||||
assert "mobileLaunch.createDisclosure" in html
|
||||
|
||||
@@ -788,6 +788,7 @@ def test_install_precaches_complete_subpath_scoped_app_shell():
|
||||
"/dashboard/static/mobile-queue-launcher.js",
|
||||
"/dashboard/static/agenda-session-launcher.js",
|
||||
"/dashboard/static/mobile-launch.js",
|
||||
"/dashboard/static/mobile-app-shortcuts.js",
|
||||
"/dashboard/static/install-app.js",
|
||||
"/dashboard/static/mobile-device-setup.js",
|
||||
"/dashboard/static/mobile-search-viewport.js",
|
||||
|
||||
@@ -82,6 +82,26 @@ async def test_pwa_assets_expose_root_scoped_share_target_without_caching_api_da
|
||||
"action": "./", "method": "GET", "enctype": "application/x-www-form-urlencoded",
|
||||
"params": {"title": "title", "text": "text", "url": "url"},
|
||||
}
|
||||
assert manifest.json()["shortcuts"] == [
|
||||
{
|
||||
"name": "Continue work", "short_name": "Continue",
|
||||
"description": "Resume the highest-priority mobile work flow.",
|
||||
"url": "./?launch=continue",
|
||||
"icons": [{"src": "static/icons/stackchain-192.png", "sizes": "192x192", "type": "image/png"}],
|
||||
},
|
||||
{
|
||||
"name": "New issue", "short_name": "New",
|
||||
"description": "Capture work now and file it online or offline.",
|
||||
"url": "./?launch=new",
|
||||
"icons": [{"src": "static/icons/stackchain-192.png", "sizes": "192x192", "type": "image/png"}],
|
||||
},
|
||||
{
|
||||
"name": "Open Agenda", "short_name": "Agenda",
|
||||
"description": "Check every assigned deadline and continue the Agenda session.",
|
||||
"url": "./?launch=agenda",
|
||||
"icons": [{"src": "static/icons/stackchain-192.png", "sizes": "192x192", "type": "image/png"}],
|
||||
},
|
||||
]
|
||||
assert worker.status_code == 200
|
||||
assert worker.headers["content-type"].startswith("application/javascript")
|
||||
assert worker.headers["service-worker-allowed"] == "/"
|
||||
|
||||
Reference in new issue
Block a user