feat(modernization): promote deferred routes to in-scope and unify page header styles

Promote /tables, /excel-query, /query-tool, /mid-section-defect from
deferred to full shell-governed in-scope routes with canonical redirects,
content contracts, governance artifacts, and updated CI gates.

Unify all page header gradients to #667eea → #764ba2 and h1 font-size
to 24px for visual consistency across all dashboard pages. Remove
Native Route-View dev annotations from job-query, excel-query, and
query-tool headers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
egg
2026-02-12 13:20:06 +08:00
parent 0ed69ce326
commit c38b5f646a
67 changed files with 1073 additions and 252 deletions

View File

@@ -89,12 +89,17 @@ class TestFullLoginLogoutFlow:
def test_complete_admin_login_workflow(self, mock_auth, _mock_is_admin, client):
"""Test complete admin login workflow."""
mock_auth.return_value = _mock_admin_user()
# 1. Access portal - should see login link
response = client.get("/")
assert response.status_code == 200
content = response.data.decode("utf-8")
assert "管理員登入" in content
spa_enabled = bool(client.application.config.get("PORTAL_SPA_ENABLED", False))
# 1. Access portal entry.
response = client.get("/", follow_redirects=False)
if spa_enabled:
assert response.status_code == 302
assert response.location.rstrip("/").endswith("/portal-shell")
else:
assert response.status_code == 200
content = response.data.decode("utf-8")
assert "管理員登入" in content
# 2. Go to login page
response = client.get("/admin/login")
@@ -106,15 +111,21 @@ class TestFullLoginLogoutFlow:
"password": "password123"
}, follow_redirects=True)
assert response.status_code == 200
content = response.data.decode("utf-8")
# Should see admin name and logout option
assert "Test Admin" in content or "登出" in content
# 4. Verify session has admin
with client.session_transaction() as sess:
assert "admin" in sess
assert sess["admin"]["mail"] == "ymirliu@panjit.com.tw"
assert response.status_code == 200
if not spa_enabled:
content = response.data.decode("utf-8")
# Portal template should show admin identity in non-SPA mode.
assert "Test Admin" in content or "登出" in content
# 4. Verify session has admin
with client.session_transaction() as sess:
assert "admin" in sess
assert sess["admin"]["mail"] == "ymirliu@panjit.com.tw"
if spa_enabled:
nav = client.get("/api/portal/navigation")
assert nav.status_code == 200
assert nav.get_json()["is_admin"] is True
# 5. Access admin pages
response = client.get("/admin/pages")
@@ -244,20 +255,22 @@ class TestPageManagementFlow:
class TestPortalDynamicTabs:
"""E2E tests for dynamic portal tabs based on page status."""
def test_portal_hides_dev_tabs_for_non_admin(self, client, temp_page_status):
"""Test portal hides dev page tabs for non-admin users."""
response = client.get("/")
assert response.status_code == 200
content = response.data.decode("utf-8")
# Released pages should show
assert "WIP 即時概況" in content
# Dev pages should NOT show (tables and resource are dev)
# Note: This depends on the can_view_page implementation in portal.html
"""E2E tests for dynamic portal tabs based on page status."""
def test_portal_hides_dev_tabs_for_non_admin(self, client, temp_page_status):
"""Test portal hides dev page tabs for non-admin users."""
response = client.get("/api/portal/navigation")
assert response.status_code == 200
payload = response.get_json()
routes = {
page["route"]
for drawer in payload.get("drawers", [])
for page in drawer.get("pages", [])
}
assert "/wip-overview" in routes
assert "/tables" not in routes
assert payload.get("is_admin") is False
@patch('mes_dashboard.routes.auth_routes.is_admin', return_value=True)
@patch('mes_dashboard.routes.auth_routes.authenticate')
def test_portal_shows_all_tabs_for_admin(self, mock_auth, _mock_is_admin, client, temp_page_status):
@@ -270,12 +283,17 @@ class TestPortalDynamicTabs:
"password": "password123"
})
response = client.get("/")
assert response.status_code == 200
content = response.data.decode("utf-8")
# Admin should see all pages
assert "WIP 即時概況" in content
response = client.get("/api/portal/navigation")
assert response.status_code == 200
payload = response.get_json()
routes = {
page["route"]
for drawer in payload.get("drawers", [])
for page in drawer.get("pages", [])
}
assert "/wip-overview" in routes
assert "/tables" in routes
assert payload.get("is_admin") is True
class TestSessionPersistence:

View File

@@ -37,27 +37,44 @@ def client(app):
class TestResourceHistoryPageAccess:
"""E2E tests for page access and navigation."""
@staticmethod
def _load_resource_history_entry(client):
spa_enabled = bool(client.application.config.get("PORTAL_SPA_ENABLED", False))
response = client.get('/resource-history', follow_redirects=False)
if spa_enabled:
assert response.status_code == 302
assert response.location.endswith('/portal-shell/resource-history')
shell_response = client.get('/portal-shell/resource-history')
assert shell_response.status_code == 200
return shell_response, True
return response, False
def test_page_loads_successfully(self, client):
"""Resource history page should load without errors."""
response = client.get('/resource-history')
response, spa_enabled = self._load_resource_history_entry(client)
assert response.status_code == 200
content = response.data.decode('utf-8')
assert '設備歷史績效' in content
if spa_enabled:
assert '/static/dist/portal-shell.js' in content
else:
assert '設備歷史績效' in content
def test_page_bootstrap_container_exists(self, client):
"""Resource history page should expose the Vue mount container."""
response = client.get('/resource-history')
response, _spa_enabled = self._load_resource_history_entry(client)
content = response.data.decode('utf-8')
assert "id='app'" in content or 'id="app"' in content
def test_page_references_vite_module(self, client):
"""Resource history page should load the Vite module bundle."""
response = client.get('/resource-history')
response, spa_enabled = self._load_resource_history_entry(client)
content = response.data.decode('utf-8')
assert '/static/dist/resource-history.js' in content
if spa_enabled:
assert '/static/dist/portal-shell.js' in content
else:
assert '/static/dist/resource-history.js' in content
assert 'type="module"' in content
@@ -329,16 +346,28 @@ class TestResourceHistoryValidation:
assert response.status_code == 200, f"Failed for granularity={granularity}"
class TestResourceHistoryNavigation:
"""E2E tests for navigation integration."""
def test_portal_includes_history_tab(self, client):
"""Portal should include resource history tab."""
response = client.get('/')
content = response.data.decode('utf-8')
assert '設備歷史績效' in content
assert 'resourceHistoryFrame' in content
class TestResourceHistoryNavigation:
"""E2E tests for navigation integration."""
def test_portal_includes_history_tab(self, client):
"""Portal should include resource history tab."""
if bool(client.application.config.get("PORTAL_SPA_ENABLED", False)):
response = client.get('/api/portal/navigation')
assert response.status_code == 200
payload = response.get_json()
pages = [
page
for drawer in payload.get("drawers", [])
for page in drawer.get("pages", [])
]
history_pages = [page for page in pages if page.get("route") == "/resource-history"]
assert history_pages, "resource-history route missing from portal navigation contract"
assert history_pages[0].get("name") == "設備歷史績效"
else:
response = client.get('/')
content = response.data.decode('utf-8')
assert '設備歷史績效' in content
assert 'resourceHistoryFrame' in content
if __name__ == '__main__':