@ripla/godd-mcp 1.0.4-canary.8 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -94
- package/dist/godd.cjs +349 -233
- package/dist/godd.js +2193 -838
- package/dist/index.js +152 -122
- package/{notes-api → notes/api}/.env.example +6 -1
- package/{notes-api → notes/api}/README.md +6 -5
- package/{notes-api → notes/api}/app/main.py +29 -2
- package/{notes-api → notes/api}/app/routers/auth.py +6 -1
- package/{notes-api → notes/api}/app/routers/issues.py +157 -1
- package/{notes-api → notes/api}/app/routers/pr.py +2 -2
- package/{notes-api → notes/api}/app/services/github_app_token.py +32 -1
- package/{notes-api → notes/api}/app/services/github_issues.py +101 -11
- package/{notes-api → notes/api}/app/services/github_pr.py +6 -1
- package/{notes-api → notes/api}/tests/test_github_app_token.py +49 -1
- package/{notes-api → notes/api}/tests/test_github_service.py +42 -0
- package/notes/api/tests/test_health.py +52 -0
- package/{notes-api → notes/api}/tests/test_issues.py +216 -0
- package/{notes-api → notes/api}/tests/test_pr.py +24 -0
- package/{notes-api → notes/api}/tests/test_pr_chain.py +19 -0
- package/{notes-api → notes/api}/uv.lock +178 -2
- package/notes/app/DESIGN.md +273 -0
- package/{notes-app → notes/app}/README.md +4 -1
- package/notes/app/docker/Dockerfile.e2e +19 -0
- package/notes/app/docker/Dockerfile.prod +15 -0
- package/notes/app/docker/Dockerfile.test +17 -0
- package/notes/app/e2e/file-tree.spec.ts +26 -0
- package/notes/app/e2e/file-view.spec.ts +25 -0
- package/notes/app/e2e/fixtures.ts +28 -0
- package/{notes-app → notes/app}/e2e/global-setup.ts +5 -7
- package/notes/app/e2e/login.spec.ts +25 -0
- package/notes/app/e2e/pages/login-page.ts +29 -0
- package/notes/app/e2e/pages/main-page.ts +29 -0
- package/notes/app/e2e/panel-resize-handle.spec.ts +213 -0
- package/{notes-app → notes/app}/eslint.config.js +9 -0
- package/{notes-app → notes/app}/package.json +0 -3
- package/{notes-app → notes/app}/pnpm-workspace.yaml +1 -0
- package/notes/app/src/components/CommentPanel.test.tsx +194 -0
- package/{notes-app → notes/app}/src/components/CommentPanel.tsx +119 -10
- package/notes/app/src/components/CommentSelectionPopover.tsx +26 -0
- package/notes/app/src/components/EditToolbar.test.tsx +102 -0
- package/{notes-app → notes/app}/src/components/EditToolbar.tsx +5 -54
- package/notes/app/src/components/FileInfoPanel.branch-tab.test.tsx +250 -0
- package/notes/app/src/components/FileInfoPanel.history-tab.test.tsx +146 -0
- package/notes/app/src/components/FileInfoPanel.test.tsx +101 -0
- package/{notes-app → notes/app}/src/components/FileInfoPanel.tsx +135 -20
- package/notes/app/src/components/IssueCommentThread.test.tsx +93 -0
- package/notes/app/src/components/IssueCommentThread.tsx +156 -0
- package/notes/app/src/components/IssueDetailView.test.tsx +1118 -0
- package/notes/app/src/components/IssueDetailView.tsx +476 -0
- package/notes/app/src/components/IssueList.test.tsx +545 -0
- package/notes/app/src/components/IssueList.tsx +374 -0
- package/{notes-app → notes/app}/src/components/LabelSelector.test.tsx +94 -8
- package/notes/app/src/components/LabelSelector.tsx +452 -0
- package/{notes-app → notes/app}/src/components/MarkdownEditor.tsx +140 -2
- package/notes/app/src/components/PanelResizeHandle.test.tsx +481 -0
- package/notes/app/src/components/PanelResizeHandle.tsx +96 -0
- package/{notes-app → notes/app}/src/hooks/useEditSession.test.ts +171 -23
- package/{notes-app → notes/app}/src/hooks/useEditSession.ts +49 -62
- package/notes/app/src/hooks/useMdUpLayout.test.ts +10 -0
- package/notes/app/src/hooks/useMdUpLayout.ts +27 -0
- package/notes/app/src/hooks/usePeriodicSync.test.ts +244 -0
- package/notes/app/src/hooks/usePeriodicSync.ts +83 -0
- package/notes/app/src/hooks/useResizablePanelWidth.test.ts +251 -0
- package/notes/app/src/hooks/useResizablePanelWidth.ts +167 -0
- package/{notes-app → notes/app}/src/lib/api.test.ts +81 -1
- package/{notes-app → notes/app}/src/lib/api.ts +98 -4
- package/notes/app/src/lib/commentAnchor.test.ts +22 -0
- package/notes/app/src/lib/commentAnchor.ts +28 -0
- package/notes/app/src/lib/commentHighlight.test.ts +110 -0
- package/notes/app/src/lib/commentHighlight.ts +44 -0
- package/notes/app/src/lib/github-sync.test.ts +126 -0
- package/notes/app/src/lib/github-sync.ts +89 -0
- package/notes/app/src/lib/issue-list-utils.test.ts +275 -0
- package/notes/app/src/lib/issue-list-utils.ts +135 -0
- package/notes/app/src/lib/issue-mobile-layout.test.ts +17 -0
- package/notes/app/src/lib/issue-mobile-layout.ts +20 -0
- package/notes/app/src/lib/issue-url-sync.test.ts +73 -0
- package/notes/app/src/lib/issue-url-sync.ts +43 -0
- package/notes/app/src/lib/main-page-default-view.test.ts +37 -0
- package/notes/app/src/lib/main-page-default-view.ts +15 -0
- package/notes/app/src/lib/resizable-panel.test.ts +199 -0
- package/notes/app/src/lib/resizable-panel.ts +136 -0
- package/notes/app/src/pages/LoginPage.test.tsx +17 -0
- package/notes/app/src/pages/MainPage.branch-modal.test.tsx +226 -0
- package/notes/app/src/pages/MainPage.issue-sync.test.tsx +145 -0
- package/notes/app/src/pages/MainPage.sidebar.test.tsx +361 -0
- package/{notes-app → notes/app}/src/pages/MainPage.tsx +406 -61
- package/{notes-app → notes/app}/src/pages/SettingsPage.test.tsx +32 -23
- package/{notes-app → notes/app}/src/pages/UserManagementPage.test.tsx +8 -18
- package/notes/app/src/test/mocks.ts +156 -0
- package/notes/app/src/test/render.tsx +27 -0
- package/package.json +3 -5
- package/notes-api/tests/test_health.py +0 -11
- package/notes-app/docker/Dockerfile.e2e +0 -14
- package/notes-app/docker/Dockerfile.prod +0 -12
- package/notes-app/docker/Dockerfile.test +0 -12
- package/notes-app/e2e/file-tree.spec.ts +0 -29
- package/notes-app/e2e/file-view.spec.ts +0 -24
- package/notes-app/e2e/login.spec.ts +0 -32
- package/notes-app/src/components/IssueDetailView.test.tsx +0 -201
- package/notes-app/src/components/IssueDetailView.tsx +0 -318
- package/notes-app/src/components/IssueList.test.tsx +0 -93
- package/notes-app/src/components/IssueList.tsx +0 -169
- package/notes-app/src/components/LabelSelector.tsx +0 -276
- package/notes-app/src/pages/LoginPage.test.tsx +0 -29
- package/stacks/django-vue.yaml +0 -33
- package/stacks/fastapi-react.yaml +0 -160
- package/stacks/flutter-firebase.yaml +0 -58
- package/stacks/nextjs-prisma.yaml +0 -33
- package/stacks/schema.json +0 -144
- package/templates/agents/architect.hbs +0 -183
- package/templates/agents/auto-documenter.hbs +0 -36
- package/templates/agents/backend-developer.hbs +0 -175
- package/templates/agents/code-reviewer.hbs +0 -29
- package/templates/agents/database-developer.hbs +0 -52
- package/templates/agents/debug-specialist.hbs +0 -39
- package/templates/agents/environment-setup.hbs +0 -41
- package/templates/agents/frontend-developer.hbs +0 -45
- package/templates/agents/integration-qa.hbs +0 -26
- package/templates/agents/performance-qa.hbs +0 -24
- package/templates/agents/pr-writer.hbs +0 -53
- package/templates/agents/quality-lead.hbs +0 -26
- package/templates/agents/requirements-analyst.hbs +0 -26
- package/templates/agents/security-analyst.hbs +0 -21
- package/templates/agents/security-reviewer.hbs +0 -20
- package/templates/agents/ssot-updater.hbs +0 -45
- package/templates/agents/technical-writer.hbs +0 -28
- package/templates/agents/unit-test-engineer.hbs +0 -19
- package/templates/agents/user-clone.hbs +0 -43
- package/templates/github-actions/aws/deploy-notes.yml.hbs +0 -117
- package/templates/github-actions/azure/deploy-notes.yml.hbs +0 -78
- package/templates/github-actions/gcp/deploy-notes.yml.hbs +0 -76
- package/templates/github-actions/vercel-railway/deploy-notes.yml.hbs +0 -49
- package/templates/mindsets/agent-stdio.hbs +0 -51
- package/templates/mindsets/dart.hbs +0 -39
- package/templates/mindsets/ddd-clean-architecture.hbs +0 -52
- package/templates/mindsets/electron.hbs +0 -41
- package/templates/mindsets/fastapi.hbs +0 -40
- package/templates/mindsets/flutter.hbs +0 -56
- package/templates/mindsets/kotlin.hbs +0 -46
- package/templates/mindsets/postgresql.hbs +0 -39
- package/templates/mindsets/python.hbs +0 -39
- package/templates/mindsets/react.hbs +0 -51
- package/templates/mindsets/swift.hbs +0 -46
- package/templates/mindsets/terraform.hbs +0 -53
- package/templates/mindsets/typescript.hbs +0 -34
- package/templates/mindsets/vite.hbs +0 -37
- package/templates/notes-compose.yml +0 -82
- package/templates/partials/cogito-geo.hbs +0 -84
- package/templates/partials/cogito-godd.hbs +0 -22
- package/templates/partials/cogito-grammar.hbs +0 -105
- package/templates/partials/cogito-particle.hbs +0 -76
- package/templates/partials/cogito-root-ai.hbs +0 -79
- package/templates/partials/cogito-root-arch.hbs +0 -75
- package/templates/partials/cogito-root-core.hbs +0 -75
- package/templates/partials/cogito-root-daily.hbs +0 -58
- package/templates/partials/cogito-root-dev.hbs +0 -71
- package/templates/partials/cogito-root-devops.hbs +0 -71
- package/templates/partials/cogito-root-framework.hbs +0 -80
- package/templates/partials/cogito-root-lang.hbs +0 -74
- package/templates/partials/godd-core.hbs +0 -50
- package/templates/partials/project-context.hbs +0 -52
- package/templates/partials/ssot-header.hbs +0 -40
- package/templates/partials/ssot-rules.hbs +0 -30
- package/templates/prompts/adr.hbs +0 -39
- package/templates/prompts/agent-dev-workflow.hbs +0 -34
- package/templates/prompts/analyze-report.hbs +0 -36
- package/templates/prompts/auto-documentation.hbs +0 -37
- package/templates/prompts/check.hbs +0 -52
- package/templates/prompts/commit.hbs +0 -96
- package/templates/prompts/dev.hbs +0 -127
- package/templates/prompts/docs-init.hbs +0 -101
- package/templates/prompts/docs-update.hbs +0 -51
- package/templates/prompts/git-workflow.hbs +0 -125
- package/templates/prompts/impact-analysis.hbs +0 -43
- package/templates/prompts/install.hbs +0 -40
- package/templates/prompts/new-branch.hbs +0 -61
- package/templates/prompts/notes-deploy.hbs +0 -138
- package/templates/prompts/pr-analyze.hbs +0 -44
- package/templates/prompts/pr-create.hbs +0 -125
- package/templates/prompts/push-execute.hbs +0 -83
- package/templates/prompts/push.hbs +0 -24
- package/templates/prompts/release-notes.hbs +0 -38
- package/templates/prompts/requirements-to-business-flow.hbs +0 -29
- package/templates/prompts/requirements-to-tickets.hbs +0 -26
- package/templates/prompts/review.hbs +0 -126
- package/templates/prompts/setup.hbs +0 -247
- package/templates/prompts/spec.hbs +0 -35
- package/templates/prompts/specification-to-tickets.hbs +0 -24
- package/templates/prompts/submit.hbs +0 -143
- package/templates/prompts/sync.hbs +0 -63
- package/templates/prompts/test-plan.hbs +0 -56
- package/templates/scripts/aws/terraform-backend-setup.sh.hbs +0 -149
- package/templates/terraform/aws/alb.tf.hbs +0 -58
- package/templates/terraform/aws/backend.tf.hbs +0 -10
- package/templates/terraform/aws/cloudfront.tf.hbs +0 -96
- package/templates/terraform/aws/ecr.tf.hbs +0 -30
- package/templates/terraform/aws/ecs.tf.hbs +0 -142
- package/templates/terraform/aws/iam.tf.hbs +0 -88
- package/templates/terraform/aws/local.tf.hbs +0 -34
- package/templates/terraform/aws/output.tf.hbs +0 -49
- package/templates/terraform/aws/provider.tf.hbs +0 -26
- package/templates/terraform/aws/rds.tf.hbs +0 -38
- package/templates/terraform/aws/s3.tf.hbs +0 -34
- package/templates/terraform/aws/secrets.tf.hbs +0 -53
- package/templates/terraform/aws/security_groups.tf.hbs +0 -81
- package/templates/terraform/aws/vpc.tf.hbs +0 -63
- package/templates/terraform/azure/backend.tf.hbs +0 -8
- package/templates/terraform/azure/iam.tf.hbs +0 -36
- package/templates/terraform/azure/main.tf.hbs +0 -203
- package/templates/terraform/azure/output.tf.hbs +0 -19
- package/templates/terraform/azure/provider.tf.hbs +0 -19
- package/templates/terraform/gcp/backend.tf.hbs +0 -6
- package/templates/terraform/gcp/iam.tf.hbs +0 -47
- package/templates/terraform/gcp/main.tf.hbs +0 -175
- package/templates/terraform/gcp/output.tf.hbs +0 -15
- package/templates/terraform/gcp/provider.tf.hbs +0 -19
- package/templates/terraform/vercel-railway/main.tf.hbs +0 -64
- package/templates/terraform/vercel-railway/output.tf.hbs +0 -11
- /package/{notes-api → notes/api}/.dockerignore +0 -0
- /package/{notes-api → notes/api}/alembic/README +0 -0
- /package/{notes-api → notes/api}/alembic/env.py +0 -0
- /package/{notes-api → notes/api}/alembic/script.py.mako +0 -0
- /package/{notes-api → notes/api}/alembic/versions/001_initial_notes_tables.py +0 -0
- /package/{notes-api → notes/api}/alembic/versions/002_add_performance_indexes.py +0 -0
- /package/{notes-api → notes/api}/alembic/versions/003_add_yjs_documents.py +0 -0
- /package/{notes-api → notes/api}/alembic/versions/004_add_comments.py +0 -0
- /package/{notes-api → notes/api}/alembic/versions/005_add_github_username.py +0 -0
- /package/{notes-api → notes/api}/alembic/versions/006_add_check_constraints.py +0 -0
- /package/{notes-api → notes/api}/alembic.ini +0 -0
- /package/{notes-api → notes/api}/app/__init__.py +0 -0
- /package/{notes-api → notes/api}/app/config.py +0 -0
- /package/{notes-api → notes/api}/app/database.py +0 -0
- /package/{notes-api → notes/api}/app/http_client.py +0 -0
- /package/{notes-api → notes/api}/app/models/__init__.py +0 -0
- /package/{notes-api → notes/api}/app/models/audit.py +0 -0
- /package/{notes-api → notes/api}/app/models/base.py +0 -0
- /package/{notes-api → notes/api}/app/models/collab.py +0 -0
- /package/{notes-api → notes/api}/app/models/comment.py +0 -0
- /package/{notes-api → notes/api}/app/models/session.py +0 -0
- /package/{notes-api → notes/api}/app/models/setting.py +0 -0
- /package/{notes-api → notes/api}/app/models/user.py +0 -0
- /package/{notes-api → notes/api}/app/path_validation.py +0 -0
- /package/{notes-api → notes/api}/app/routers/__init__.py +0 -0
- /package/{notes-api → notes/api}/app/routers/collab.py +0 -0
- /package/{notes-api → notes/api}/app/routers/comments.py +0 -0
- /package/{notes-api → notes/api}/app/routers/files.py +0 -0
- /package/{notes-api → notes/api}/app/routers/settings.py +0 -0
- /package/{notes-api → notes/api}/app/routers/tree.py +0 -0
- /package/{notes-api → notes/api}/app/routers/upload.py +0 -0
- /package/{notes-api → notes/api}/app/routers/users.py +0 -0
- /package/{notes-api → notes/api}/app/schemas/__init__.py +0 -0
- /package/{notes-api → notes/api}/app/schemas/auth.py +0 -0
- /package/{notes-api → notes/api}/app/schemas/user.py +0 -0
- /package/{notes-api → notes/api}/app/security.py +0 -0
- /package/{notes-api → notes/api}/app/services/__init__.py +0 -0
- /package/{notes-api → notes/api}/app/services/audit.py +0 -0
- /package/{notes-api → notes/api}/app/services/business_date.py +0 -0
- /package/{notes-api → notes/api}/app/services/collab.py +0 -0
- /package/{notes-api → notes/api}/app/services/github.py +0 -0
- /package/{notes-api → notes/api}/app/services/pr_chain.py +0 -0
- /package/{notes-api → notes/api}/app/startup.py +0 -0
- /package/{notes-api → notes/api}/docker/Dockerfile +0 -0
- /package/{notes-api → notes/api}/docker/Dockerfile.test +0 -0
- /package/{notes-api → notes/api}/pyproject.toml +0 -0
- /package/{notes-api → notes/api}/tests/conftest.py +0 -0
- /package/{notes-api → notes/api}/tests/test_audit.py +0 -0
- /package/{notes-api → notes/api}/tests/test_auth.py +0 -0
- /package/{notes-api → notes/api}/tests/test_business_date.py +0 -0
- /package/{notes-api → notes/api}/tests/test_collab.py +0 -0
- /package/{notes-api → notes/api}/tests/test_comments.py +0 -0
- /package/{notes-api → notes/api}/tests/test_config_ssl.py +0 -0
- /package/{notes-api → notes/api}/tests/test_config_warnings.py +0 -0
- /package/{notes-api → notes/api}/tests/test_files.py +0 -0
- /package/{notes-api → notes/api}/tests/test_path_validation.py +0 -0
- /package/{notes-api → notes/api}/tests/test_security.py +0 -0
- /package/{notes-api → notes/api}/tests/test_settings.py +0 -0
- /package/{notes-api → notes/api}/tests/test_tree.py +0 -0
- /package/{notes-api → notes/api}/tests/test_upload.py +0 -0
- /package/{notes-api → notes/api}/tests/test_users.py +0 -0
- /package/{notes-app → notes/app}/.dockerignore +0 -0
- /package/{notes-app → notes/app}/index.html +0 -0
- /package/{notes-app → notes/app}/nginx.conf +0 -0
- /package/{notes-app → notes/app}/package-lock.json +0 -0
- /package/{notes-app → notes/app}/playwright.config.ts +0 -0
- /package/{notes-app → notes/app}/pnpm-lock.yaml +0 -0
- /package/{notes-app → notes/app}/src/App.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/BranchSelectorModal.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/CodeBlockView.test.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/CodeBlockView.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/CollabStatus.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/DiffPreview.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/DrawioEditor.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/DrawioViewer.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/ErrorBoundary.test.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/ErrorBoundary.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/FileContentView.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/FileContextMenu.test.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/FileContextMenu.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/FileDialogs.test.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/FileDialogs.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/SaveStatusIndicator.test.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/SaveStatusIndicator.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/Skeleton.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/SpreadsheetEditor.test.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/SpreadsheetEditor.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/TextEditor.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/ToastContainer.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/TreeNode.tsx +0 -0
- /package/{notes-app → notes/app}/src/contexts/AuthContext.test.tsx +0 -0
- /package/{notes-app → notes/app}/src/contexts/AuthContext.tsx +0 -0
- /package/{notes-app → notes/app}/src/contexts/ToastContext.test.ts +0 -0
- /package/{notes-app → notes/app}/src/contexts/ToastContext.tsx +0 -0
- /package/{notes-app → notes/app}/src/hooks/useAutoSave.test.ts +0 -0
- /package/{notes-app → notes/app}/src/hooks/useAutoSave.ts +0 -0
- /package/{notes-app → notes/app}/src/hooks/useCollaboration.ts +0 -0
- /package/{notes-app → notes/app}/src/hooks/useComments.ts +0 -0
- /package/{notes-app → notes/app}/src/hooks/useYText.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/branch-diff.test.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/branch-diff.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/created-file.test.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/created-file.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/csv-utils.test.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/csv-utils.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/deepLinkPathToSearch.test.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/deepLinkPathToSearch.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/draft-storage.test.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/draft-storage.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/mermaid-render.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/postLoginRedirect.test.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/postLoginRedirect.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/tree-utils.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/user-colors.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/xlsx-utils.test.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/xlsx-utils.ts +0 -0
- /package/{notes-app → notes/app}/src/main.tsx +0 -0
- /package/{notes-app → notes/app}/src/pages/LoginPage.tsx +0 -0
- /package/{notes-app → notes/app}/src/pages/MainPage.insertChildEntry.test.ts +0 -0
- /package/{notes-app → notes/app}/src/pages/SettingsPage.tsx +0 -0
- /package/{notes-app → notes/app}/src/pages/UserManagementPage.tsx +0 -0
- /package/{notes-app → notes/app}/src/styles/globals.css +0 -0
- /package/{notes-app → notes/app}/src/types.ts +0 -0
- /package/{notes-app → notes/app}/src/vite-env.d.ts +0 -0
- /package/{notes-app → notes/app}/tsconfig.json +0 -0
- /package/{notes-app → notes/app}/tsconfig.node.json +0 -0
- /package/{notes-app → notes/app}/vite.config.js +0 -0
- /package/{notes-app → notes/app}/vite.config.ts +0 -0
- /package/{notes-app → notes/app}/vitest.config.ts +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
FROM node:22-alpine
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
# pnpm バージョンは compose の x-pnpm-version アンカー(build-arg PNPM_VERSION)を正本とする。
|
|
6
|
+
# ARG default は standalone `docker build` 用フォールバックで root packageManager と一致させる(#984 / #1004)。
|
|
7
|
+
ARG PNPM_VERSION=10.15.0
|
|
8
|
+
RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate
|
|
9
|
+
|
|
10
|
+
COPY package.json pnpm-lock.yaml ./
|
|
11
|
+
# pnpm v10 は無視されたビルドスクリプトをエラーにする(ERR_PNPM_IGNORED_BUILDS)ため、
|
|
12
|
+
# CI(.github/workflows/ci.yml)と同様に --ignore-scripts でインストールし esbuild のみ明示 rebuild する。
|
|
13
|
+
RUN pnpm install --frozen-lockfile --ignore-scripts && pnpm rebuild esbuild
|
|
14
|
+
|
|
15
|
+
COPY . .
|
|
16
|
+
|
|
17
|
+
CMD ["sh", "-c", "pnpm lint && pnpm test && pnpm build"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { test, expect } from "./fixtures";
|
|
2
|
+
|
|
3
|
+
test.describe("File tree (mock mode)", () => {
|
|
4
|
+
test("displays mock tree entries on main page", async ({ mainPage }) => {
|
|
5
|
+
await mainPage.goto();
|
|
6
|
+
|
|
7
|
+
await expect(mainPage.sidebar.getByText("README.md")).toBeVisible();
|
|
8
|
+
await expect(mainPage.sidebar.getByText("001_project")).toBeVisible();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("expands directory to show children", async ({ page, mainPage }) => {
|
|
12
|
+
await mainPage.goto();
|
|
13
|
+
|
|
14
|
+
await page.getByRole("button", { name: /001_project/ }).click();
|
|
15
|
+
|
|
16
|
+
await expect(page.getByText("features.csv")).toBeVisible();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("selects file and updates URL", async ({ page, mainPage }) => {
|
|
20
|
+
await mainPage.goto();
|
|
21
|
+
|
|
22
|
+
await page.getByText("README.md").first().click();
|
|
23
|
+
|
|
24
|
+
await expect(page).toHaveURL(/[?&]file=docs.*README\.md/);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { test, expect } from "./fixtures";
|
|
2
|
+
|
|
3
|
+
const README_FILE = "docs/README.md";
|
|
4
|
+
|
|
5
|
+
test.describe("File viewing (mock mode)", () => {
|
|
6
|
+
test("shows markdown content when a .md file is selected", async ({ mainPage }) => {
|
|
7
|
+
await mainPage.goto(README_FILE);
|
|
8
|
+
|
|
9
|
+
await expect(mainPage.content.locator(".prose")).toBeVisible({ timeout: 10_000 });
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("shows edit button for authenticated user", async ({ mainPage }) => {
|
|
13
|
+
await mainPage.goto(README_FILE);
|
|
14
|
+
|
|
15
|
+
await expect(mainPage.editButton).toBeVisible();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("enters edit mode on edit button click", async ({ mainPage }) => {
|
|
19
|
+
await mainPage.goto(README_FILE);
|
|
20
|
+
|
|
21
|
+
await mainPage.editButton.click();
|
|
22
|
+
|
|
23
|
+
await expect(mainPage.editor).toBeVisible({ timeout: 10_000 });
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { test as base } from "@playwright/test";
|
|
2
|
+
import { LoginPage } from "./pages/login-page";
|
|
3
|
+
import { MainPage } from "./pages/main-page";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* E2E 共通フィクスチャ(#974)。
|
|
7
|
+
* Page Object とテスト認証情報を一元化し、各 spec はシナリオ固有の操作・検証のみを持つ。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** テスト用認証情報。global-setup と login spec で共有する。 */
|
|
11
|
+
export const TEST_USERNAME = process.env.TEST_USERNAME ?? "admin";
|
|
12
|
+
export const TEST_PASSWORD = process.env.TEST_PASSWORD ?? "admin123";
|
|
13
|
+
|
|
14
|
+
interface Fixtures {
|
|
15
|
+
loginPage: LoginPage;
|
|
16
|
+
mainPage: MainPage;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const test = base.extend<Fixtures>({
|
|
20
|
+
loginPage: async ({ page }, use) => {
|
|
21
|
+
await use(new LoginPage(page));
|
|
22
|
+
},
|
|
23
|
+
mainPage: async ({ page }, use) => {
|
|
24
|
+
await use(new MainPage(page));
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export { expect } from "@playwright/test";
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import { test as setup, expect } from "@playwright/test";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { LoginPage } from "./pages/login-page";
|
|
5
|
+
import { TEST_USERNAME, TEST_PASSWORD } from "./fixtures";
|
|
4
6
|
|
|
5
7
|
const AUTH_FILE = "e2e/.auth/user.json";
|
|
6
8
|
|
|
7
9
|
setup("authenticate", async ({ page }) => {
|
|
8
10
|
fs.mkdirSync(path.dirname(AUTH_FILE), { recursive: true });
|
|
9
11
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
await page.goto("/login");
|
|
14
|
-
await page.locator("#login-username").fill(username);
|
|
15
|
-
await page.locator("#login-password").fill(password);
|
|
16
|
-
await page.locator('button[type="submit"]').click();
|
|
12
|
+
const loginPage = new LoginPage(page);
|
|
13
|
+
await loginPage.goto();
|
|
14
|
+
await loginPage.login(TEST_USERNAME, TEST_PASSWORD);
|
|
17
15
|
|
|
18
16
|
await expect(page).not.toHaveURL(/\/login/);
|
|
19
17
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { test, expect, TEST_USERNAME, TEST_PASSWORD } from "./fixtures";
|
|
2
|
+
|
|
3
|
+
test.describe("Login flow", () => {
|
|
4
|
+
test.use({ storageState: { cookies: [], origins: [] } });
|
|
5
|
+
|
|
6
|
+
test("redirects unauthenticated user to /login", async ({ page, mainPage }) => {
|
|
7
|
+
await mainPage.goto();
|
|
8
|
+
await expect(page).toHaveURL(/\/login/);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("logs in with valid credentials", async ({ page, loginPage }) => {
|
|
12
|
+
await loginPage.goto();
|
|
13
|
+
await loginPage.login(TEST_USERNAME, TEST_PASSWORD);
|
|
14
|
+
|
|
15
|
+
await expect(page).not.toHaveURL(/\/login/);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("shows error for invalid credentials", async ({ page, loginPage }) => {
|
|
19
|
+
await loginPage.goto();
|
|
20
|
+
await loginPage.login("wrong-user-e2e", "wrong-pass-e2e");
|
|
21
|
+
|
|
22
|
+
await expect(loginPage.errorAlert).toBeVisible();
|
|
23
|
+
await expect(page).toHaveURL(/\/login/);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Locator, Page } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ログイン画面の Page Object(#974)。
|
|
5
|
+
* ログインフォームの主要ロケータと認証操作を集約し、global-setup と login spec が共有する。
|
|
6
|
+
*/
|
|
7
|
+
export class LoginPage {
|
|
8
|
+
readonly usernameInput: Locator;
|
|
9
|
+
readonly passwordInput: Locator;
|
|
10
|
+
readonly submitButton: Locator;
|
|
11
|
+
readonly errorAlert: Locator;
|
|
12
|
+
|
|
13
|
+
constructor(private readonly page: Page) {
|
|
14
|
+
this.usernameInput = page.locator("#login-username");
|
|
15
|
+
this.passwordInput = page.locator("#login-password");
|
|
16
|
+
this.submitButton = page.locator('button[type="submit"]');
|
|
17
|
+
this.errorAlert = page.locator('[role="alert"]');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async goto(): Promise<void> {
|
|
21
|
+
await this.page.goto("/login");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async login(username: string, password: string): Promise<void> {
|
|
25
|
+
await this.usernameInput.fill(username);
|
|
26
|
+
await this.passwordInput.fill(password);
|
|
27
|
+
await this.submitButton.click();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Locator, Page } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* メイン画面(ファイルツリー + コンテンツ表示)の Page Object(#974)。
|
|
5
|
+
* ベース遷移と主要ロケータ(サイドバー / コンテンツ / 編集ボタン / エディタ)を集約する。
|
|
6
|
+
* ツリー項目の選択など各シナリオ固有の操作は spec 側に残す。
|
|
7
|
+
*/
|
|
8
|
+
export class MainPage {
|
|
9
|
+
/** ファイルツリーを含むサイドバー */
|
|
10
|
+
readonly sidebar: Locator;
|
|
11
|
+
/** ファイル内容を表示するメイン領域 */
|
|
12
|
+
readonly content: Locator;
|
|
13
|
+
/** 編集モードへ切り替えるボタン */
|
|
14
|
+
readonly editButton: Locator;
|
|
15
|
+
/** 編集モードの Tiptap / ProseMirror エディタ */
|
|
16
|
+
readonly editor: Locator;
|
|
17
|
+
|
|
18
|
+
constructor(private readonly page: Page) {
|
|
19
|
+
this.sidebar = page.locator("aside").first();
|
|
20
|
+
this.content = page.locator("main");
|
|
21
|
+
this.editButton = page.getByRole("button", { name: /編集/ });
|
|
22
|
+
this.editor = page.locator(".tiptap, .ProseMirror");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** ルートへ遷移する。`file` を渡すと `?file=<path>` クエリ付きで開く。 */
|
|
26
|
+
async goto(file?: string): Promise<void> {
|
|
27
|
+
await this.page.goto(file ? `/?file=${file}` : "/");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { test, expect } from "./fixtures";
|
|
2
|
+
import type { Locator, Page } from "@playwright/test";
|
|
3
|
+
import type { MainPage } from "./pages/main-page";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* REQ-UI-AC-05 / #1008: リサイズハンドルの視覚線(hover/drag インジケーター)と親 aside の
|
|
7
|
+
* border(border-r / border-l)の **ピクセル一致** を実ブラウザ(Playwright)で検証する視覚回帰テスト。
|
|
8
|
+
*
|
|
9
|
+
* jsdom はレイアウトエンジンを持たず className トークン一致しか検証できない(既存
|
|
10
|
+
* PanelResizeHandle.test.tsx)。サブピクセルの実位置は #991 / PR #1007 で `edge` 別 translate
|
|
11
|
+
* (right=translate-x-0 / left=-translate-x-full)により 0.5px ずれを修正済みだが、実ピクセル位置の
|
|
12
|
+
* 回帰は jsdom では検出できない。本テストは getBoundingClientRect の数値比較(スナップショット比較より
|
|
13
|
+
* 安定・軽量。issue #1008 推奨方針 (B))で、視覚線中心と border 中心の x 座標一致を検証する。
|
|
14
|
+
*
|
|
15
|
+
* 視覚線の幾何は hover / drag(group-active) / 既定で不変(状態で変わるのは色のみ。focus-visible のみ
|
|
16
|
+
* w-px→w-0.5 で幅変化)。したがって hover と drag の双方で同一位置に重なることを確認する。
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const README_FILE = "docs/README.md";
|
|
20
|
+
|
|
21
|
+
// 視覚線中心と border 中心の許容差(px)。#991 の回帰はちょうど 0.5px ずれるため、それを確実に
|
|
22
|
+
// 検出できるよう 0.5px 未満に設定する(修正版は設計上 0px 一致)。
|
|
23
|
+
const ALIGN_TOLERANCE_PX = 0.25;
|
|
24
|
+
|
|
25
|
+
// group-active(ドラッグ)状態にするためのポインタ移動量(px)。min/max クランプに掛からない小さな値。
|
|
26
|
+
const DRAG_DELTA_PX = 8;
|
|
27
|
+
|
|
28
|
+
// translate / boundingBox 判定の許容差(px)。サブピクセル丸めを吸収する小さな値。
|
|
29
|
+
const ONSCREEN_TOLERANCE_PX = 0.5;
|
|
30
|
+
|
|
31
|
+
interface Alignment {
|
|
32
|
+
lineCenter: number;
|
|
33
|
+
borderCenter: number;
|
|
34
|
+
lineWidth: number;
|
|
35
|
+
borderWidth: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** ハンドル内視覚線の中心 x と、親 aside の border 中心 x を実測して返す。 */
|
|
39
|
+
async function measureAlignment(
|
|
40
|
+
handle: Locator,
|
|
41
|
+
aside: Locator,
|
|
42
|
+
side: "left" | "right",
|
|
43
|
+
): Promise<Alignment> {
|
|
44
|
+
const border = await aside.evaluate((el, s) => {
|
|
45
|
+
const r = el.getBoundingClientRect();
|
|
46
|
+
const cs = getComputedStyle(el);
|
|
47
|
+
const width = parseFloat(s === "right" ? cs.borderRightWidth : cs.borderLeftWidth);
|
|
48
|
+
// border-box の border は要素の外縁 1px。right 端は [right-w, right]、left 端は [left, left+w]。
|
|
49
|
+
const center = s === "right" ? r.right - width / 2 : r.left + width / 2;
|
|
50
|
+
return { center, width };
|
|
51
|
+
}, side);
|
|
52
|
+
|
|
53
|
+
const line = await handle.evaluate((el) => {
|
|
54
|
+
// ハンドル(role=slider)の唯一の子 div が視覚線。
|
|
55
|
+
const lineEl = el.firstElementChild as HTMLElement;
|
|
56
|
+
const r = lineEl.getBoundingClientRect();
|
|
57
|
+
return { center: (r.left + r.right) / 2, width: r.width };
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
lineCenter: line.center,
|
|
62
|
+
borderCenter: border.center,
|
|
63
|
+
lineWidth: line.width,
|
|
64
|
+
borderWidth: border.width,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* aside が md+ で水平 translate されておらず、viewport 内に収まることを検証する(#1009)。
|
|
70
|
+
* ハンドルは aside の子で、aside ごと translate されても視覚線と border の相対整列は保たれ偽陽性になるため、
|
|
71
|
+
* パネル自体の物理位置を独立に確認する。
|
|
72
|
+
*
|
|
73
|
+
* 重要: Tailwind v4 の `translate-x-*` は `transform` ではなく **`translate` プロパティ**に値を入れる
|
|
74
|
+
* (`translate: var(--tw-translate-x) var(--tw-translate-y)`)。よって `transform` は常に `none` になり判定に使えない。
|
|
75
|
+
* ドロワー退避(`max-md:rtl:translate-x-full`)が md+ に漏れると `translate` の x 成分が要素幅ぶん非ゼロになるため、
|
|
76
|
+
* `translate` プロパティの x 成分で判定する。
|
|
77
|
+
*/
|
|
78
|
+
async function expectAsideOnScreen(aside: Locator, page: Page): Promise<void> {
|
|
79
|
+
const translateX = await aside.evaluate((el) => {
|
|
80
|
+
const t = getComputedStyle(el).translate; // "none" | "<x>[ <y>]"(px 解決値)
|
|
81
|
+
return t === "none" || t === "" ? 0 : Number.parseFloat(t);
|
|
82
|
+
});
|
|
83
|
+
expect(Math.abs(translateX)).toBeLessThanOrEqual(ONSCREEN_TOLERANCE_PX);
|
|
84
|
+
const box = await aside.boundingBox();
|
|
85
|
+
if (!box) throw new Error("aside の boundingBox を取得できない");
|
|
86
|
+
const viewport = page.viewportSize();
|
|
87
|
+
if (!viewport) throw new Error("viewport を取得できない");
|
|
88
|
+
expect(box.x).toBeGreaterThanOrEqual(-ONSCREEN_TOLERANCE_PX);
|
|
89
|
+
expect(box.x + box.width).toBeLessThanOrEqual(viewport.width + ONSCREEN_TOLERANCE_PX);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function expectAligned(m: Alignment): void {
|
|
93
|
+
// 視覚線・border とも 1px 幅であること(w-px / border 1px)。
|
|
94
|
+
expect(m.lineWidth).toBeCloseTo(1, 1);
|
|
95
|
+
expect(m.borderWidth).toBeCloseTo(1, 1);
|
|
96
|
+
// 中心 x が同一ピクセル位置に重なること。
|
|
97
|
+
expect(Math.abs(m.lineCenter - m.borderCenter)).toBeLessThanOrEqual(ALIGN_TOLERANCE_PX);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** ハンドル中央で pointer down + 移動し、group-active(ドラッグ)状態にする。呼び出し側で mouse.up する。 */
|
|
101
|
+
async function startDrag(handle: Locator, page: Page): Promise<void> {
|
|
102
|
+
const box = await handle.boundingBox();
|
|
103
|
+
if (!box) throw new Error("リサイズハンドルの boundingBox を取得できない");
|
|
104
|
+
const cx = box.x + box.width / 2;
|
|
105
|
+
const cy = box.y + box.height / 2;
|
|
106
|
+
await page.mouse.move(cx, cy);
|
|
107
|
+
await page.mouse.down();
|
|
108
|
+
// 小さく移動して group-active を有効化(過大ドラッグで min/max クランプに掛からない範囲)。
|
|
109
|
+
await page.mouse.move(cx + DRAG_DELTA_PX, cy);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** 編集モードで base 参照パネル(右パネル / border-l)を開き、その左端ハンドルと aside を返す。 */
|
|
113
|
+
async function openRightReferencePanel(
|
|
114
|
+
mainPage: MainPage,
|
|
115
|
+
page: Page,
|
|
116
|
+
): Promise<{ handle: Locator; aside: Locator }> {
|
|
117
|
+
await mainPage.goto(README_FILE);
|
|
118
|
+
await expect(mainPage.content.locator(".prose")).toBeVisible({ timeout: 10_000 });
|
|
119
|
+
await mainPage.editButton.click();
|
|
120
|
+
await expect(mainPage.editor).toBeVisible({ timeout: 10_000 });
|
|
121
|
+
await page.getByRole("button", { name: /^base:/ }).click();
|
|
122
|
+
const handle = page.getByRole("slider", { name: "参照パネルの幅を変更" });
|
|
123
|
+
await expect(handle).toBeVisible();
|
|
124
|
+
const aside = page.locator("aside").filter({ has: handle });
|
|
125
|
+
return { handle, aside };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
test.describe("PanelResizeHandle 視覚線と border のピクセル一致 (REQ-UI-AC-05 / #1008)", () => {
|
|
129
|
+
// ハンドルは md+ のみ表示(`hidden md:block` / resolveMdUpPanelResize)。md+ 前提を明示し、
|
|
130
|
+
// Playwright 既定 viewport(DPR=1)への暗黙依存を排して許容差比較を安定させる。
|
|
131
|
+
test.use({ viewport: { width: 1280, height: 720 } });
|
|
132
|
+
|
|
133
|
+
test("左サイドバー右端ハンドル: hover 時に視覚線が border-r と一致する", async ({ mainPage, page }) => {
|
|
134
|
+
await mainPage.goto();
|
|
135
|
+
const handle = page.getByRole("slider", { name: "左サイドバーの幅を変更" });
|
|
136
|
+
await expect(handle).toBeVisible();
|
|
137
|
+
|
|
138
|
+
await handle.hover();
|
|
139
|
+
|
|
140
|
+
expectAligned(await measureAlignment(handle, mainPage.sidebar, "right"));
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("左サイドバー右端ハンドル: drag 中も視覚線が border-r と一致する", async ({ mainPage, page }) => {
|
|
144
|
+
await mainPage.goto();
|
|
145
|
+
const handle = page.getByRole("slider", { name: "左サイドバーの幅を変更" });
|
|
146
|
+
await expect(handle).toBeVisible();
|
|
147
|
+
|
|
148
|
+
await startDrag(handle, page);
|
|
149
|
+
try {
|
|
150
|
+
expectAligned(await measureAlignment(handle, mainPage.sidebar, "right"));
|
|
151
|
+
} finally {
|
|
152
|
+
await page.mouse.up();
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("右パネル左端ハンドル: hover 時に視覚線が border-l と一致する", async ({ mainPage, page }) => {
|
|
157
|
+
const { handle, aside } = await openRightReferencePanel(mainPage, page);
|
|
158
|
+
|
|
159
|
+
await handle.hover();
|
|
160
|
+
|
|
161
|
+
expectAligned(await measureAlignment(handle, aside, "left"));
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("右パネル左端ハンドル: drag 中も視覚線が border-l と一致する", async ({ mainPage, page }) => {
|
|
165
|
+
const { handle, aside } = await openRightReferencePanel(mainPage, page);
|
|
166
|
+
|
|
167
|
+
await startDrag(handle, page);
|
|
168
|
+
try {
|
|
169
|
+
expectAligned(await measureAlignment(handle, aside, "left"));
|
|
170
|
+
} finally {
|
|
171
|
+
await page.mouse.up();
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* #1009: 物理プロパティ→論理プロパティ移行の RTL ガード。`dir="rtl"` では論理 border / inset が
|
|
178
|
+
* 物理左右を反転させるため、ハンドルの視覚線が「反転後の物理側」の border と一致することを実ブラウザで検証する。
|
|
179
|
+
* jsdom はレイアウトエンジンを持たず、論理プロパティの実反転(物理側の特定)を検証できないため E2E でガードする。
|
|
180
|
+
*/
|
|
181
|
+
test.describe("PanelResizeHandle RTL での border 一致 (#1009 / REQ-UI-AC-12)", () => {
|
|
182
|
+
test.use({ viewport: { width: 1280, height: 720 } });
|
|
183
|
+
|
|
184
|
+
test("RTL: 左サイドバーの border-e は物理左へ反転し、視覚線がそれと一致する", async ({ mainPage, page }) => {
|
|
185
|
+
await mainPage.goto();
|
|
186
|
+
await page.evaluate(() => {
|
|
187
|
+
document.documentElement.setAttribute("dir", "rtl");
|
|
188
|
+
});
|
|
189
|
+
const handle = page.getByRole("slider", { name: "左サイドバーの幅を変更" });
|
|
190
|
+
await expect(handle).toBeVisible();
|
|
191
|
+
|
|
192
|
+
// #1009 リグレッションガード: md+ では RTL でもドロワーの translate を残さない
|
|
193
|
+
// (`max-md:rtl:translate-x-full` が md+ に漏れると red になる)。
|
|
194
|
+
const sidebar = mainPage.sidebar;
|
|
195
|
+
await expectAsideOnScreen(sidebar, page);
|
|
196
|
+
|
|
197
|
+
await handle.hover();
|
|
198
|
+
|
|
199
|
+
// LTR では border-e=物理右だが、RTL では border-e=物理左。視覚線も論理 inset で同じ側へ移る。
|
|
200
|
+
expectAligned(await measureAlignment(handle, sidebar, "left"));
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("RTL: 右参照パネルの border-s は物理右へ反転し、視覚線がそれと一致する", async ({ mainPage, page }) => {
|
|
204
|
+
const { handle, aside } = await openRightReferencePanel(mainPage, page);
|
|
205
|
+
await page.evaluate(() => {
|
|
206
|
+
document.documentElement.setAttribute("dir", "rtl");
|
|
207
|
+
});
|
|
208
|
+
await handle.hover();
|
|
209
|
+
|
|
210
|
+
// LTR では border-s=物理左だが、RTL では border-s=物理右。
|
|
211
|
+
expectAligned(await measureAlignment(handle, aside, "right"));
|
|
212
|
+
});
|
|
213
|
+
});
|
|
@@ -20,4 +20,13 @@ export default defineConfig([
|
|
|
20
20
|
globals: globals.browser,
|
|
21
21
|
},
|
|
22
22
|
},
|
|
23
|
+
{
|
|
24
|
+
// Playwright E2E は React アプリではないため React 専用ルールを除外する(#974)。
|
|
25
|
+
// 特に Playwright フィクスチャの慣用引数 `use` を react-hooks が React Hook と誤検出するのを防ぐ。
|
|
26
|
+
files: ["e2e/**/*.ts"],
|
|
27
|
+
rules: {
|
|
28
|
+
"react-hooks/rules-of-hooks": "off",
|
|
29
|
+
"react-refresh/only-export-components": "off",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
23
32
|
]);
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
|
3
|
+
import CommentPanel from "./CommentPanel";
|
|
4
|
+
import type { UseCommentsReturn } from "@/hooks/useComments";
|
|
5
|
+
import type { UserInfo } from "@/lib/api";
|
|
6
|
+
|
|
7
|
+
const mockUser: UserInfo = {
|
|
8
|
+
id: "user-1",
|
|
9
|
+
username: "alice",
|
|
10
|
+
displayName: "Alice",
|
|
11
|
+
role: "editor",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const mockComment = {
|
|
15
|
+
id: "c1",
|
|
16
|
+
file_path: "docs/readme.md",
|
|
17
|
+
body: "Original body",
|
|
18
|
+
author_id: "user-1",
|
|
19
|
+
author_name: "Alice",
|
|
20
|
+
parent_id: null,
|
|
21
|
+
anchor_text: "selected text",
|
|
22
|
+
position_from: 0,
|
|
23
|
+
position_to: 13,
|
|
24
|
+
resolved_at: null,
|
|
25
|
+
resolved_by: null,
|
|
26
|
+
created_at: "2026-05-25T00:00:00Z",
|
|
27
|
+
updated_at: "2026-05-25T00:00:00Z",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function buildCommentsMock(overrides: Partial<UseCommentsReturn> = {}): UseCommentsReturn {
|
|
31
|
+
return {
|
|
32
|
+
comments: [mockComment],
|
|
33
|
+
loading: false,
|
|
34
|
+
error: null,
|
|
35
|
+
includeResolved: false,
|
|
36
|
+
reload: vi.fn(),
|
|
37
|
+
setIncludeResolved: vi.fn(),
|
|
38
|
+
addComment: vi.fn().mockResolvedValue(mockComment),
|
|
39
|
+
editComment: vi.fn().mockResolvedValue(true),
|
|
40
|
+
resolveComment: vi.fn().mockResolvedValue(true),
|
|
41
|
+
removeComment: vi.fn().mockResolvedValue(true),
|
|
42
|
+
...overrides,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("CommentPanel", () => {
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
vi.clearAllMocks();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("pendingAnchor 付き投稿で anchor フィールドを addComment に渡す", async () => {
|
|
52
|
+
const addComment = vi.fn().mockResolvedValue(mockComment);
|
|
53
|
+
render(
|
|
54
|
+
<CommentPanel
|
|
55
|
+
filePath="docs/readme.md"
|
|
56
|
+
currentUser={mockUser}
|
|
57
|
+
comments={buildCommentsMock({ addComment })}
|
|
58
|
+
pendingAnchor={{
|
|
59
|
+
anchor_text: "hello",
|
|
60
|
+
position_from: 1,
|
|
61
|
+
position_to: 6,
|
|
62
|
+
}}
|
|
63
|
+
onClearAnchor={vi.fn()}
|
|
64
|
+
onClose={vi.fn()}
|
|
65
|
+
/>,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
fireEvent.change(screen.getByPlaceholderText(/コメントを入力/), {
|
|
69
|
+
target: { value: "New comment" },
|
|
70
|
+
});
|
|
71
|
+
fireEvent.click(screen.getByRole("button", { name: "投稿" }));
|
|
72
|
+
|
|
73
|
+
await waitFor(() => {
|
|
74
|
+
expect(addComment).toHaveBeenCalledWith({
|
|
75
|
+
file_path: "docs/readme.md",
|
|
76
|
+
body: "New comment",
|
|
77
|
+
anchor_text: "hello",
|
|
78
|
+
position_from: 1,
|
|
79
|
+
position_to: 6,
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("著者がコメントを編集できる", async () => {
|
|
85
|
+
const editComment = vi.fn().mockResolvedValue(true);
|
|
86
|
+
render(
|
|
87
|
+
<CommentPanel
|
|
88
|
+
filePath="docs/readme.md"
|
|
89
|
+
currentUser={mockUser}
|
|
90
|
+
comments={buildCommentsMock({ editComment })}
|
|
91
|
+
pendingAnchor={null}
|
|
92
|
+
onClearAnchor={vi.fn()}
|
|
93
|
+
onClose={vi.fn()}
|
|
94
|
+
/>,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
fireEvent.click(screen.getByRole("button", { name: "編集" }));
|
|
98
|
+
const textarea = screen.getAllByRole("textbox")[0];
|
|
99
|
+
fireEvent.change(textarea, { target: { value: "Updated body" } });
|
|
100
|
+
fireEvent.click(screen.getByRole("button", { name: "保存" }));
|
|
101
|
+
|
|
102
|
+
await waitFor(() => {
|
|
103
|
+
expect(editComment).toHaveBeenCalledWith("c1", "Updated body");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("panelResize.width を aside の inline width に適用する", () => {
|
|
108
|
+
const { container } = render(
|
|
109
|
+
<CommentPanel
|
|
110
|
+
filePath="docs/readme.md"
|
|
111
|
+
currentUser={mockUser}
|
|
112
|
+
comments={buildCommentsMock()}
|
|
113
|
+
pendingAnchor={null}
|
|
114
|
+
onClearAnchor={vi.fn()}
|
|
115
|
+
onClose={vi.fn()}
|
|
116
|
+
panelResize={{
|
|
117
|
+
width: 420,
|
|
118
|
+
minWidth: 240,
|
|
119
|
+
maxWidth: 560,
|
|
120
|
+
onPointerDown: vi.fn(),
|
|
121
|
+
onWidthChange: vi.fn(),
|
|
122
|
+
}}
|
|
123
|
+
/>,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const aside = container.querySelector("aside");
|
|
127
|
+
expect(aside?.getAttribute("style")).toContain("width: 420px");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("panelResize 指定時に右パネル用リサイズハンドルを表示する", () => {
|
|
131
|
+
render(
|
|
132
|
+
<CommentPanel
|
|
133
|
+
filePath="docs/readme.md"
|
|
134
|
+
currentUser={mockUser}
|
|
135
|
+
comments={buildCommentsMock()}
|
|
136
|
+
pendingAnchor={null}
|
|
137
|
+
onClearAnchor={vi.fn()}
|
|
138
|
+
onClose={vi.fn()}
|
|
139
|
+
panelResize={{
|
|
140
|
+
width: 320,
|
|
141
|
+
minWidth: 240,
|
|
142
|
+
maxWidth: 560,
|
|
143
|
+
onPointerDown: vi.fn(),
|
|
144
|
+
onWidthChange: vi.fn(),
|
|
145
|
+
}}
|
|
146
|
+
/>,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
expect(
|
|
150
|
+
screen.getByRole("slider", { name: "コメントパネルの幅を変更" }),
|
|
151
|
+
).toBeTruthy();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("showResizeHandle: false のとき幅は適用するがハンドルは非表示", () => {
|
|
155
|
+
const { container } = render(
|
|
156
|
+
<CommentPanel
|
|
157
|
+
filePath="docs/readme.md"
|
|
158
|
+
currentUser={mockUser}
|
|
159
|
+
comments={buildCommentsMock()}
|
|
160
|
+
pendingAnchor={null}
|
|
161
|
+
onClearAnchor={vi.fn()}
|
|
162
|
+
onClose={vi.fn()}
|
|
163
|
+
panelResize={{
|
|
164
|
+
width: 360,
|
|
165
|
+
minWidth: 240,
|
|
166
|
+
maxWidth: 560,
|
|
167
|
+
onPointerDown: vi.fn(),
|
|
168
|
+
onWidthChange: vi.fn(),
|
|
169
|
+
showResizeHandle: false,
|
|
170
|
+
}}
|
|
171
|
+
/>,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
expect(container.querySelector("aside")?.getAttribute("style")).toContain("width: 360px");
|
|
175
|
+
expect(screen.queryByRole("slider")).toBeNull();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("md+ 用に aside を relative 配置する", () => {
|
|
179
|
+
const { container } = render(
|
|
180
|
+
<CommentPanel
|
|
181
|
+
filePath="docs/readme.md"
|
|
182
|
+
currentUser={mockUser}
|
|
183
|
+
comments={buildCommentsMock()}
|
|
184
|
+
pendingAnchor={null}
|
|
185
|
+
onClearAnchor={vi.fn()}
|
|
186
|
+
onClose={vi.fn()}
|
|
187
|
+
/>,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const aside = container.querySelector("aside");
|
|
191
|
+
expect(aside?.className).toContain("md:relative");
|
|
192
|
+
expect(aside?.className).not.toContain("md:static");
|
|
193
|
+
});
|
|
194
|
+
});
|