@nexttylabs/echo 0.4.0 → 0.5.0
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/CHANGELOG.md +13 -0
- package/app/(public)/[organizationSlug]/roadmap/page.tsx +19 -1
- package/app/api/admin/backup/route.ts +22 -4
- package/app/api/auth/register/handler.ts +1 -2
- package/lib/auth/config.ts +0 -7
- package/lib/db/migrations/0000_needy_leech.sql +335 -0
- package/lib/db/migrations/meta/0000_snapshot.json +2186 -1
- package/lib/db/migrations/meta/_journal.json +2 -135
- package/lib/db/schema/auth.ts +0 -1
- package/lib/db/schema/index.ts +0 -1
- package/lib/portal/public-context.tsx +5 -0
- package/package.json +20 -1
- package/.changeset/README.md +0 -21
- package/.changeset/config.json +0 -11
- package/.changeset/cozy-ghosts-care.md +0 -5
- package/.changeset/sharp-lines-stand.md +0 -5
- package/.changeset/sour-doodles-eat.md +0 -5
- package/.changeset/tender-moose-shop.md +0 -5
- package/.github/pull_request_template.md +0 -13
- package/.github/workflows/ci.yml +0 -41
- package/.github/workflows/publish.yml +0 -44
- package/.github/workflows/release.yml +0 -73
- package/AGENTS.md +0 -92
- package/Dockerfile +0 -57
- package/Makefile +0 -77
- package/bun.lock +0 -2503
- package/components/portal/project-switcher.tsx +0 -20
- package/docker-compose.dev.yml +0 -26
- package/docker-compose.yml +0 -98
- package/docs/architecture.md +0 -259
- package/docs/component-inventory.md +0 -261
- package/docs/database-migrations.md +0 -76
- package/docs/development-guide.md +0 -209
- package/docs/e2e-user-flows.csv +0 -31
- package/docs/er-diagram-feedback.mmd +0 -138
- package/docs/er-diagram.mmd +0 -281
- package/docs/i18n-check-report.md +0 -296
- package/docs/index.md +0 -214
- package/docs/logic-chain.md +0 -94
- package/docs/plans/2026-01-02-database-migration-scripts.md +0 -496
- package/docs/plans/2026-01-02-user-login-design.md +0 -37
- package/docs/plans/2026-01-02-user-login.md +0 -437
- package/docs/plans/2026-01-02-user-registration-design.md +0 -47
- package/docs/plans/2026-01-02-user-registration.md +0 -628
- package/docs/plans/2026-01-03-roles-permissions-design.md +0 -20
- package/docs/plans/2026-01-03-roles-permissions.md +0 -266
- package/docs/plans/2026-01-05-authentication-middleware.md +0 -207
- package/docs/plans/2026-01-05-member-removal.md +0 -186
- package/docs/plans/2026-01-05-organization-creation.md +0 -374
- package/docs/plans/2026-01-05-rbac-middleware.md +0 -112
- package/docs/plans/2026-01-05-role-configuration.md +0 -441
- package/docs/plans/2026-01-06-file-upload-support.md +0 -804
- package/docs/plans/2026-01-06-permission-check-hook.md +0 -155
- package/docs/plans/2026-01-06-resource-ownership-check.md +0 -231
- package/docs/plans/2026-01-07-feedback-tracking-link.md +0 -459
- package/docs/plans/2026-01-09-logout-redirect-design.md +0 -52
- package/docs/plans/2026-01-09-phase2-3-plan.md +0 -654
- package/docs/plans/2026-01-09-portal-execution-plan.md +0 -408
- package/docs/plans/2026-01-09-project-delete-feature-design.md +0 -163
- package/docs/plans/2026-01-09-project-delete-implementation.md +0 -451
- package/docs/plans/2026-01-09-project-edit-delete-design.md +0 -52
- package/docs/plans/2026-01-09-settings-center-design.md +0 -114
- package/docs/plans/2026-01-09-settings-center.md +0 -948
- package/docs/plans/2026-01-10-organization-only-design.md +0 -66
- package/docs/plans/2026-01-10-organization-only-implementation.md +0 -433
- package/docs/plans/2026-01-10-portal-settings-restructure-plan.md +0 -18
- package/docs/plans/2026-01-10-project-settings-tabs-design-implementation.md +0 -296
- package/docs/plans/2026-01-14-e2e-playwright-feedback.md +0 -173
- package/docs/plans/2026-01-15-feedback-management-org-context-design.md +0 -82
- package/docs/plans/2026-01-15-feedback-management-org-context-implementation-plan.md +0 -521
- package/docs/plans/2026-01-16-admin-feedback-filters-design.md +0 -75
- package/docs/plans/2026-01-16-admin-feedback-filters-implementation.md +0 -293
- package/docs/plans/2026-01-16-admin-feedback-route-consolidation.md +0 -180
- package/docs/plans/2026-01-16-e2e-test-fixes.md +0 -158
- package/docs/plans/2026-01-17-admin-feedback-filters.md +0 -214
- package/docs/plans/2026-01-17-admin-feedback-improvements.md +0 -453
- package/docs/plans/2026-01-18-changesets-design.md +0 -40
- package/docs/product_changes.md +0 -37
- package/docs/project-overview.md +0 -159
- package/docs/project-scan-report.json +0 -104
- package/docs/route-role-visibility.md +0 -51
- package/docs/source-tree-analysis.md +0 -150
- package/docs/testing/delete-project-manual-tests.md +0 -18
- package/docs/user-story-tracking.md +0 -191
- package/eslint.config.mjs +0 -19
- package/lib/db/migrations/.gitkeep +0 -0
- package/lib/db/migrations/0000_cynical_gladiator.sql +0 -53
- package/lib/db/migrations/0001_wandering_sunfire.sql +0 -27
- package/lib/db/migrations/0002_shallow_speedball.sql +0 -1
- package/lib/db/migrations/0003_add_org_description.sql +0 -1
- package/lib/db/migrations/0003_boring_wild_pack.sql +0 -13
- package/lib/db/migrations/0004_windy_tyrannus.sql +0 -27
- package/lib/db/migrations/0005_perpetual_doorman.sql +0 -5
- package/lib/db/migrations/0006_aberrant_captain_midlands.sql +0 -13
- package/lib/db/migrations/0007_clever_captain_cross.sql +0 -14
- package/lib/db/migrations/0008_sparkling_pandemic.sql +0 -2
- package/lib/db/migrations/0009_happy_black_tom.sql +0 -29
- package/lib/db/migrations/0010_kind_junta.sql +0 -8
- package/lib/db/migrations/0011_mute_squadron_supreme.sql +0 -25
- package/lib/db/migrations/0012_giant_power_man.sql +0 -24
- package/lib/db/migrations/0013_damp_titanium_man.sql +0 -17
- package/lib/db/migrations/0014_blue_alice.sql +0 -18
- package/lib/db/migrations/0015_webhook_tables.sql +0 -41
- package/lib/db/migrations/0016_github_integration.sql +0 -30
- package/lib/db/migrations/0016_overjoyed_ghost_rider.sql +0 -22
- package/lib/db/migrations/0017_slimy_inhumans.sql +0 -6
- package/lib/db/migrations/0018_same_spitfire.sql +0 -1
- package/lib/db/migrations/0019_jittery_loners.sql +0 -16
- package/lib/db/migrations/0019_remove_projects_add_org_settings.sql +0 -14
- package/lib/db/migrations/meta/0001_snapshot.json +0 -553
- package/lib/db/migrations/meta/0002_snapshot.json +0 -560
- package/lib/db/migrations/meta/0003_snapshot.json +0 -650
- package/lib/db/migrations/meta/0004_snapshot.json +0 -852
- package/lib/db/migrations/meta/0005_snapshot.json +0 -900
- package/lib/db/migrations/meta/0006_snapshot.json +0 -1011
- package/lib/db/migrations/meta/0007_snapshot.json +0 -1125
- package/lib/db/migrations/meta/0008_snapshot.json +0 -1146
- package/lib/db/migrations/meta/0009_snapshot.json +0 -1386
- package/lib/db/migrations/meta/0010_snapshot.json +0 -1419
- package/lib/db/migrations/meta/0011_snapshot.json +0 -1615
- package/lib/db/migrations/meta/0012_snapshot.json +0 -1805
- package/lib/db/migrations/meta/0013_snapshot.json +0 -1948
- package/lib/db/migrations/meta/0014_snapshot.json +0 -2082
- package/lib/db/migrations/meta/0015_snapshot.json +0 -2476
- package/lib/db/migrations/meta/0016_snapshot.json +0 -2633
- package/lib/db/migrations/meta/0017_snapshot.json +0 -2680
- package/lib/db/migrations/meta/0018_snapshot.json +0 -2686
- package/lib/db/migrations/meta/0019_snapshot.json +0 -2741
- package/lib/db/schema/projects.ts +0 -145
- package/lib/db/schema/user-profiles.ts +0 -31
- package/lib/validations/projects.ts +0 -49
- package/next-env.d.ts +0 -6
- package/playwright.config.ts +0 -44
- package/proxy.test.ts +0 -131
- package/proxy.ts +0 -116
- package/scripts/backup-db.sh +0 -57
- package/scripts/backup-db.ts +0 -24
- package/scripts/generate-openapi.ts +0 -22
- package/scripts/migration-helper.ts +0 -39
- package/scripts/pre-deploy.ts +0 -75
- package/scripts/restore-db.sh +0 -60
- package/scripts/rollback.ts +0 -72
- package/scripts/seed-tags.ts +0 -48
- package/tests/api/feedback-bulk.test.ts +0 -47
- package/tests/api/feedback-by-id.test.ts +0 -67
- package/tests/api/feedback-comments-route-import.test.ts +0 -26
- package/tests/api/feedback-create.test.ts +0 -71
- package/tests/api/feedback-delete.test.ts +0 -160
- package/tests/api/feedback-filter.test.ts +0 -250
- package/tests/api/feedback-list.test.ts +0 -234
- package/tests/api/feedback-route-assignee-condition.test.ts +0 -32
- package/tests/api/feedback-similar.test.ts +0 -46
- package/tests/api/feedback-sort.test.ts +0 -261
- package/tests/api/feedback-status-enum.test.ts +0 -49
- package/tests/api/feedback-status-filter.test.ts +0 -117
- package/tests/api/feedback-submit-on-behalf.test.ts +0 -269
- package/tests/api/feedback.test.ts +0 -175
- package/tests/api/identify-jwt.test.ts +0 -25
- package/tests/api/invitation-accept.test.ts +0 -213
- package/tests/api/organization-invitations.test.ts +0 -186
- package/tests/api/organization-members-list.test.ts +0 -79
- package/tests/api/organization-members.test.ts +0 -340
- package/tests/api/organizations.test.ts +0 -149
- package/tests/api/register.test.ts +0 -112
- package/tests/api/upload.test.ts +0 -103
- package/tests/api/vote.test.ts +0 -82
- package/tests/app/admin-feedback-detail-page.test.tsx +0 -25
- package/tests/app/admin-feedback-list-page.test.tsx +0 -25
- package/tests/app/admin-feedback-new-page.test.tsx +0 -25
- package/tests/app/health-route-helpers.test.ts +0 -27
- package/tests/app/login-page.test.ts +0 -26
- package/tests/app/portal-page.test.ts +0 -29
- package/tests/app/project-portal-overview.test.tsx +0 -25
- package/tests/app/widget-page-import.test.ts +0 -25
- package/tests/components/create-post-dialog-defaults.test.ts +0 -43
- package/tests/components/feedback/duplicate-suggestions-inline.test.tsx +0 -27
- package/tests/components/feedback/embedded-feedback-form.test.tsx +0 -96
- package/tests/components/feedback/feedback-detail.test.tsx +0 -25
- package/tests/components/feedback/feedback-stats.test.tsx +0 -49
- package/tests/components/feedback-bulk-actions.test.tsx +0 -39
- package/tests/components/feedback-i18n-keys.test.ts +0 -70
- package/tests/components/feedback-list-controls-compile.test.ts +0 -25
- package/tests/components/feedback-list-controls.test.tsx +0 -204
- package/tests/components/feedback-list-item.test.tsx +0 -67
- package/tests/components/landing/hero.test.tsx +0 -46
- package/tests/components/layout/language-switcher.test.tsx +0 -25
- package/tests/components/layout/sidebar.test.tsx +0 -157
- package/tests/components/login-form.test.ts +0 -25
- package/tests/components/organization-form.test.ts +0 -32
- package/tests/components/organization-switcher.test.ts +0 -25
- package/tests/components/pagination.test.tsx +0 -43
- package/tests/components/portal-overview.test.tsx +0 -25
- package/tests/components/profile-form.test.tsx +0 -139
- package/tests/components/role-selector.test.ts +0 -31
- package/tests/components/status-chart.test.tsx +0 -90
- package/tests/e2e/auth.e2e.ts +0 -323
- package/tests/e2e/feedback-actions.e2e.ts +0 -471
- package/tests/e2e/feedback-attachment.e2e.ts +0 -168
- package/tests/e2e/feedback-customer.e2e.ts +0 -226
- package/tests/e2e/feedback-management.e2e.ts +0 -565
- package/tests/e2e/feedback-submit.e2e.ts +0 -133
- package/tests/e2e/feedback-view.e2e.ts +0 -297
- package/tests/e2e/fixtures/test-data.ts +0 -235
- package/tests/e2e/health-check.e2e.ts +0 -230
- package/tests/e2e/helpers/test-utils-helpers.test.ts +0 -43
- package/tests/e2e/helpers/test-utils.ts +0 -298
- package/tests/e2e/integration-placeholders.e2e.ts +0 -199
- package/tests/e2e/organization.e2e.ts +0 -292
- package/tests/e2e/permissions.e2e.ts +0 -424
- package/tests/e2e/project-widget.e2e.ts +0 -63
- package/tests/feedback/filters.test.ts +0 -29
- package/tests/hooks/use-permissions.test.ts +0 -52
- package/tests/lib/ai/classifier.test.ts +0 -104
- package/tests/lib/ai/duplicate-detector.test.ts +0 -234
- package/tests/lib/attachments-schema.test.ts +0 -30
- package/tests/lib/auth/session.test.ts +0 -49
- package/tests/lib/auth-client.test.ts +0 -37
- package/tests/lib/auth-config.test.ts +0 -26
- package/tests/lib/feedback-prefill.test.ts +0 -52
- package/tests/lib/feedback-processor.test.ts +0 -41
- package/tests/lib/feedback-schema.test.ts +0 -33
- package/tests/lib/file-validator.test.ts +0 -48
- package/tests/lib/get-feedback-by-id.test.ts +0 -37
- package/tests/lib/invitations.test.ts +0 -35
- package/tests/lib/login-schema.test.ts +0 -36
- package/tests/lib/org-context.test.ts +0 -95
- package/tests/lib/organization-access.test.ts +0 -44
- package/tests/lib/organization-member-role-schema.test.ts +0 -41
- package/tests/lib/permissions.test.ts +0 -88
- package/tests/lib/portal-analytics.test.ts +0 -25
- package/tests/lib/portal-contributors.test.ts +0 -25
- package/tests/lib/portal-copy.test.ts +0 -27
- package/tests/lib/portal-i18n.test.ts +0 -30
- package/tests/lib/portal-leaderboard-settings.test.ts +0 -25
- package/tests/lib/portal-modules.test.ts +0 -25
- package/tests/lib/portal-seo.test.ts +0 -25
- package/tests/lib/portal-sharing.test.ts +0 -25
- package/tests/lib/portal-sorting.test.ts +0 -25
- package/tests/lib/portal-theme.test.ts +0 -25
- package/tests/lib/rate-limit.test.ts +0 -142
- package/tests/lib/resolve-locale.test.ts +0 -34
- package/tests/lib/services/backup.test.ts +0 -145
- package/tests/lib/user-organizations.test.ts +0 -42
- package/tests/lib/user-role-schema.test.ts +0 -33
- package/tests/lib/user-schema.test.ts +0 -25
- package/tests/setup.ts +0 -74
- package/vercel.json +0 -4
|
@@ -1,565 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2026 Echo Team
|
|
3
|
-
*
|
|
4
|
-
* This program is free software: you can redistribute it and/or modify
|
|
5
|
-
* it under the terms of the GNU Affero General Public License as published by
|
|
6
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
-
* (at your option) any later version.
|
|
8
|
-
*
|
|
9
|
-
* This program is distributed in the hope that it will be useful,
|
|
10
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
-
* GNU Affero General Public License for more details.
|
|
13
|
-
*
|
|
14
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { test, expect, uniqueEmail, uniqueName, TestHelpers } from "./helpers/test-utils";
|
|
19
|
-
import { TestDataManager, createTestFeedback } from "./fixtures/test-data";
|
|
20
|
-
|
|
21
|
-
test.describe("E2E-UF-007: View feedback list", () => {
|
|
22
|
-
let helpers: TestHelpers;
|
|
23
|
-
let testDataManager: TestDataManager;
|
|
24
|
-
let organizationId: string;
|
|
25
|
-
|
|
26
|
-
test.beforeEach(async ({ page, request }) => {
|
|
27
|
-
helpers = new TestHelpers(page, request);
|
|
28
|
-
|
|
29
|
-
const email = uniqueEmail();
|
|
30
|
-
const name = uniqueName();
|
|
31
|
-
const password = "StrongPass123!";
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
await helpers.registerAndLogin(name, email, password);
|
|
35
|
-
testDataManager = new TestDataManager(page.request);
|
|
36
|
-
|
|
37
|
-
// Get organization ID from registration response (authenticated request context isn't shared)
|
|
38
|
-
organizationId = helpers.getOrganizationId();
|
|
39
|
-
|
|
40
|
-
// Create test feedback
|
|
41
|
-
for (let i = 0; i < 5; i++) {
|
|
42
|
-
await testDataManager.createFeedback({
|
|
43
|
-
...createTestFeedback({
|
|
44
|
-
title: `Test Feedback ${i + 1}`,
|
|
45
|
-
description: `Description for feedback ${i + 1}`,
|
|
46
|
-
}),
|
|
47
|
-
organizationId,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("loads feedback list with default view", async ({ page }) => {
|
|
53
|
-
await page.goto("/admin/feedback");
|
|
54
|
-
|
|
55
|
-
// Should show feedback list
|
|
56
|
-
await expect(page.getByRole("heading", { name: /反馈|feedback/i })).toBeVisible();
|
|
57
|
-
|
|
58
|
-
// Should show feedback items
|
|
59
|
-
const feedbackItems = page.locator('[data-testid="feedback-item"], .feedback-item');
|
|
60
|
-
await expect(feedbackItems.first()).toBeVisible();
|
|
61
|
-
|
|
62
|
-
// Should show key fields
|
|
63
|
-
await expect(page.getByText("Test Feedback 1")).toBeVisible();
|
|
64
|
-
await expect(page.getByText("Description for feedback 1")).toBeVisible();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test("shows pagination if feedback exceeds page limit", async ({ page }) => {
|
|
68
|
-
// Create more feedback to trigger pagination
|
|
69
|
-
for (let i = 5; i < 25; i++) {
|
|
70
|
-
await testDataManager.createFeedback({
|
|
71
|
-
...createTestFeedback({
|
|
72
|
-
title: `Extra Feedback ${i}`,
|
|
73
|
-
description: `Extra description ${i}`,
|
|
74
|
-
}),
|
|
75
|
-
organizationId,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
await page.goto("/admin/feedback");
|
|
80
|
-
|
|
81
|
-
// Should see pagination controls
|
|
82
|
-
const pagination = page.locator('[data-testid="pagination"], .pagination');
|
|
83
|
-
if (await pagination.isVisible()) {
|
|
84
|
-
await expect(pagination).toBeVisible();
|
|
85
|
-
|
|
86
|
-
// Should show page numbers
|
|
87
|
-
await expect(page.getByRole("button", { name: /2/i })).toBeVisible();
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test("displays feedback metadata in list", async ({ page }) => {
|
|
92
|
-
await page.goto("/admin/feedback");
|
|
93
|
-
|
|
94
|
-
// Check for metadata columns
|
|
95
|
-
const statusColumn = page.locator('[data-testid="status-column"], th:has-text("Status")');
|
|
96
|
-
const dateColumn = page.locator('[data-testid="date-column"], th:has-text("Date")');
|
|
97
|
-
const votesColumn = page.locator('[data-testid="votes-column"], th:has-text("Votes")');
|
|
98
|
-
|
|
99
|
-
if (await statusColumn.isVisible()) {
|
|
100
|
-
await expect(statusColumn).toBeVisible();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (await dateColumn.isVisible()) {
|
|
104
|
-
await expect(dateColumn).toBeVisible();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (await votesColumn.isVisible()) {
|
|
108
|
-
await expect(votesColumn).toBeVisible();
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test.describe("E2E-UF-008: Filter feedback by status", () => {
|
|
114
|
-
let helpers: TestHelpers;
|
|
115
|
-
let testDataManager: TestDataManager;
|
|
116
|
-
let organizationId: string;
|
|
117
|
-
|
|
118
|
-
test.beforeEach(async ({ page, request }) => {
|
|
119
|
-
helpers = new TestHelpers(page, request);
|
|
120
|
-
|
|
121
|
-
const email = uniqueEmail();
|
|
122
|
-
const name = uniqueName();
|
|
123
|
-
const password = "StrongPass123!";
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
await helpers.registerAndLogin(name, email, password);
|
|
127
|
-
testDataManager = new TestDataManager(page.request);
|
|
128
|
-
|
|
129
|
-
// Get organization ID from registration response (authenticated request context isn't shared)
|
|
130
|
-
organizationId = helpers.getOrganizationId();
|
|
131
|
-
|
|
132
|
-
// Create feedback with different statuses
|
|
133
|
-
await testDataManager.createFeedback({
|
|
134
|
-
...createTestFeedback({ title: "Open Feedback" }),
|
|
135
|
-
organizationId,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
await testDataManager.createFeedback({
|
|
139
|
-
...createTestFeedback({ title: "In Progress Feedback" }),
|
|
140
|
-
organizationId,
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
await testDataManager.createFeedback({
|
|
144
|
-
...createTestFeedback({ title: "Completed Feedback" }),
|
|
145
|
-
organizationId,
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test("filters feedback by status successfully", async ({ page }) => {
|
|
150
|
-
await page.goto("/admin/feedback");
|
|
151
|
-
|
|
152
|
-
// Find status filter
|
|
153
|
-
const statusFilter = page.locator('[data-testid="status-filter"], select[name="status"]');
|
|
154
|
-
if (await statusFilter.isVisible()) {
|
|
155
|
-
// Filter by "Open" status
|
|
156
|
-
await statusFilter.click();
|
|
157
|
-
await page.getByRole("option", { name: /open/i }).click();
|
|
158
|
-
|
|
159
|
-
// Wait for filter to apply
|
|
160
|
-
await page.waitForTimeout(500);
|
|
161
|
-
|
|
162
|
-
// Should only show open feedback
|
|
163
|
-
await expect(page.getByText("Open Feedback")).toBeVisible();
|
|
164
|
-
await expect(page.getByText("In Progress Feedback")).not.toBeVisible();
|
|
165
|
-
await expect(page.getByText("Completed Feedback")).not.toBeVisible();
|
|
166
|
-
} else {
|
|
167
|
-
// Alternative: click on status tabs or buttons
|
|
168
|
-
const openTab = page.locator('[data-testid="status-tab-open"], button:has-text("Open")');
|
|
169
|
-
if (await openTab.isVisible()) {
|
|
170
|
-
await openTab.click();
|
|
171
|
-
await expect(page.getByText("Open Feedback")).toBeVisible();
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
test("shows active filter indicator", async ({ page }) => {
|
|
177
|
-
await page.goto("/admin/feedback");
|
|
178
|
-
|
|
179
|
-
const statusFilter = page.locator('[data-testid="status-filter"], select[name="status"]');
|
|
180
|
-
if (await statusFilter.isVisible()) {
|
|
181
|
-
await statusFilter.click();
|
|
182
|
-
await page.getByRole("option", { name: /in progress/i }).click();
|
|
183
|
-
|
|
184
|
-
// Should show active filter
|
|
185
|
-
await expect(page.getByText(/filter.*in progress|status.*in progress/i)).toBeVisible();
|
|
186
|
-
|
|
187
|
-
// Should show clear filter option
|
|
188
|
-
const clearFilter = page.locator('[data-testid="clear-filter"], button:has-text("Clear")');
|
|
189
|
-
if (await clearFilter.isVisible()) {
|
|
190
|
-
await expect(clearFilter).toBeVisible();
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
test("clears filter and shows all feedback", async ({ page }) => {
|
|
196
|
-
await page.goto("/admin/feedback");
|
|
197
|
-
|
|
198
|
-
const statusFilter = page.locator('[data-testid="status-filter"], select[name="status"]');
|
|
199
|
-
if (await statusFilter.isVisible()) {
|
|
200
|
-
// Apply filter
|
|
201
|
-
await statusFilter.click();
|
|
202
|
-
await page.getByRole("option", { name: /open/i }).click();
|
|
203
|
-
await page.waitForTimeout(500);
|
|
204
|
-
|
|
205
|
-
// Clear filter
|
|
206
|
-
const clearFilter = page.locator('[data-testid="clear-filter"], button:has-text("Clear")');
|
|
207
|
-
if (await clearFilter.isVisible()) {
|
|
208
|
-
await clearFilter.click();
|
|
209
|
-
} else {
|
|
210
|
-
// Or select "All" option
|
|
211
|
-
await statusFilter.click();
|
|
212
|
-
await page.getByRole("option", { name: /all/i }).click();
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Should show all feedback again
|
|
216
|
-
await expect(page.getByText("Open Feedback")).toBeVisible();
|
|
217
|
-
await expect(page.getByText("In Progress Feedback")).toBeVisible();
|
|
218
|
-
await expect(page.getByText("Completed Feedback")).toBeVisible();
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
test.describe("E2E-UF-009: Sort feedback by votes and date", () => {
|
|
224
|
-
let helpers: TestHelpers;
|
|
225
|
-
let testDataManager: TestDataManager;
|
|
226
|
-
let organizationId: string;
|
|
227
|
-
|
|
228
|
-
test.beforeEach(async ({ page, request }) => {
|
|
229
|
-
helpers = new TestHelpers(page, request);
|
|
230
|
-
|
|
231
|
-
const email = uniqueEmail();
|
|
232
|
-
const name = uniqueName();
|
|
233
|
-
const password = "StrongPass123!";
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
await helpers.registerAndLogin(name, email, password);
|
|
237
|
-
testDataManager = new TestDataManager(page.request);
|
|
238
|
-
|
|
239
|
-
// Get organization ID from registration response (authenticated request context isn't shared)
|
|
240
|
-
organizationId = helpers.getOrganizationId();
|
|
241
|
-
|
|
242
|
-
// Create feedback with different dates
|
|
243
|
-
await testDataManager.createFeedback({
|
|
244
|
-
...createTestFeedback({ title: "Oldest Feedback" }),
|
|
245
|
-
organizationId,
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
// Wait a bit to ensure different timestamps
|
|
249
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
250
|
-
|
|
251
|
-
await testDataManager.createFeedback({
|
|
252
|
-
...createTestFeedback({ title: "Newest Feedback" }),
|
|
253
|
-
organizationId,
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
test("sorts feedback by vote count", async ({ page }) => {
|
|
258
|
-
await page.goto("/admin/feedback");
|
|
259
|
-
|
|
260
|
-
// Find sort dropdown
|
|
261
|
-
const sortSelect = page.locator('[data-testid="sort-select"], select[name="sort"]');
|
|
262
|
-
if (await sortSelect.isVisible()) {
|
|
263
|
-
// Sort by votes
|
|
264
|
-
await sortSelect.click();
|
|
265
|
-
await page.getByRole("option", { name: /votes/i }).click();
|
|
266
|
-
|
|
267
|
-
// Wait for sort to apply
|
|
268
|
-
await page.waitForTimeout(500);
|
|
269
|
-
|
|
270
|
-
// Should show sorted indicator
|
|
271
|
-
await expect(page.getByText(/sorted.*votes|sort.*votes/i)).toBeVisible();
|
|
272
|
-
} else {
|
|
273
|
-
// Alternative: click on column headers
|
|
274
|
-
const votesHeader = page.locator('[data-testid="votes-header"], th:has-text("Votes")');
|
|
275
|
-
if (await votesHeader.isVisible()) {
|
|
276
|
-
await votesHeader.click();
|
|
277
|
-
await expect(page.getByText(/sorted.*votes/i)).toBeVisible();
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
test("sorts feedback by creation date", async ({ page }) => {
|
|
283
|
-
await page.goto("/admin/feedback");
|
|
284
|
-
|
|
285
|
-
const sortSelect = page.locator('[data-testid="sort-select"], select[name="sort"]');
|
|
286
|
-
if (await sortSelect.isVisible()) {
|
|
287
|
-
// Sort by date (newest first)
|
|
288
|
-
await sortSelect.click();
|
|
289
|
-
await page.getByRole("option", { name: /date|newest/i }).click();
|
|
290
|
-
|
|
291
|
-
await page.waitForTimeout(500);
|
|
292
|
-
|
|
293
|
-
// Newest should be first
|
|
294
|
-
const firstItem = page.locator('[data-testid="feedback-item"]').first();
|
|
295
|
-
await expect(firstItem.getByText("Newest Feedback")).toBeVisible();
|
|
296
|
-
|
|
297
|
-
// Sort by date (oldest first)
|
|
298
|
-
await sortSelect.click();
|
|
299
|
-
await page.getByRole("option", { name: /oldest/i }).click();
|
|
300
|
-
|
|
301
|
-
await page.waitForTimeout(500);
|
|
302
|
-
|
|
303
|
-
// Oldest should be first
|
|
304
|
-
await expect(firstItem.getByText("Oldest Feedback")).toBeVisible();
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
test("toggles sort direction", async ({ page }) => {
|
|
309
|
-
await page.goto("/admin/feedback");
|
|
310
|
-
|
|
311
|
-
// Click on date header to sort
|
|
312
|
-
const dateHeader = page.locator('[data-testid="date-header"], th:has-text("Date")');
|
|
313
|
-
if (await dateHeader.isVisible()) {
|
|
314
|
-
await dateHeader.click();
|
|
315
|
-
|
|
316
|
-
// Should show sort indicator
|
|
317
|
-
await expect(dateHeader.locator('[data-testid="sort-indicator"], .sort-indicator')).toBeVisible();
|
|
318
|
-
|
|
319
|
-
// Click again to reverse
|
|
320
|
-
await dateHeader.click();
|
|
321
|
-
|
|
322
|
-
// Sort direction should change
|
|
323
|
-
const sortIndicator = dateHeader.locator('[data-testid="sort-indicator"]');
|
|
324
|
-
await expect(sortIndicator).toHaveClass(/desc|asc/);
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
test.describe("E2E-UF-010: View feedback details", () => {
|
|
330
|
-
let helpers: TestHelpers;
|
|
331
|
-
let testDataManager: TestDataManager;
|
|
332
|
-
let organizationId: string;
|
|
333
|
-
let feedbackId: number;
|
|
334
|
-
|
|
335
|
-
test.beforeEach(async ({ page, request }) => {
|
|
336
|
-
helpers = new TestHelpers(page, request);
|
|
337
|
-
|
|
338
|
-
const email = uniqueEmail();
|
|
339
|
-
const name = uniqueName();
|
|
340
|
-
const password = "StrongPass123!";
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
await helpers.registerAndLogin(name, email, password);
|
|
344
|
-
testDataManager = new TestDataManager(page.request);
|
|
345
|
-
|
|
346
|
-
// Get organization ID from registration response (authenticated request context isn't shared)
|
|
347
|
-
organizationId = helpers.getOrganizationId();
|
|
348
|
-
|
|
349
|
-
// Create test feedback
|
|
350
|
-
const feedback = await testDataManager.createFeedback({
|
|
351
|
-
...createTestFeedback({
|
|
352
|
-
title: "Detailed Feedback",
|
|
353
|
-
description: "This is a detailed description with multiple lines.\n\nIt has important information.",
|
|
354
|
-
type: "bug",
|
|
355
|
-
priority: "high",
|
|
356
|
-
}),
|
|
357
|
-
organizationId,
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
feedbackId = feedback.feedbackId;
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
test("shows complete feedback information", async ({ page }) => {
|
|
364
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
365
|
-
|
|
366
|
-
// Should show all feedback fields
|
|
367
|
-
await expect(page.getByRole("heading", { name: "Detailed Feedback" })).toBeVisible();
|
|
368
|
-
await expect(page.getByText(/This is a detailed description/)).toBeVisible();
|
|
369
|
-
|
|
370
|
-
// Should show metadata
|
|
371
|
-
await expect(page.getByText("Bug")).toBeVisible();
|
|
372
|
-
await expect(page.getByText(/优先级:\s*高/)).toBeVisible();
|
|
373
|
-
|
|
374
|
-
// Should show status selector
|
|
375
|
-
await expect(page.getByRole("combobox")).toBeVisible();
|
|
376
|
-
|
|
377
|
-
// Should show timestamps
|
|
378
|
-
await expect(page.getByText("创建时间")).toBeVisible();
|
|
379
|
-
await expect(page.getByText("更新时间")).toBeVisible();
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
test("navigates from list to detail view", async ({ page }) => {
|
|
383
|
-
await page.goto("/admin/feedback");
|
|
384
|
-
|
|
385
|
-
// Click on feedback item
|
|
386
|
-
const feedbackItem = page.locator('[data-testid="feedback-item"]:has-text("Detailed Feedback")');
|
|
387
|
-
await feedbackItem.click();
|
|
388
|
-
|
|
389
|
-
// Should navigate to detail page
|
|
390
|
-
await page.waitForURL(/\/admin\/feedback\/[^\/]+$/);
|
|
391
|
-
await expect(page.getByRole("heading", { name: "Detailed Feedback" })).toBeVisible();
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
test("shows edit and action buttons for authorized users", async ({ page }) => {
|
|
395
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
396
|
-
|
|
397
|
-
// Should see action buttons
|
|
398
|
-
const editButton = page.locator('button').filter({ hasText: /edit/i });
|
|
399
|
-
if (await editButton.isVisible()) {
|
|
400
|
-
await expect(editButton).toBeVisible();
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const deleteButton = page.locator('button').filter({ hasText: /delete/i });
|
|
404
|
-
if (await deleteButton.isVisible()) {
|
|
405
|
-
await expect(deleteButton).toBeVisible();
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
const statusSelect = page.locator('[data-testid="status-select"]');
|
|
409
|
-
if (await statusSelect.isVisible()) {
|
|
410
|
-
await expect(statusSelect).toBeVisible();
|
|
411
|
-
}
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
test("displays feedback activity history", async ({ page }) => {
|
|
415
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
416
|
-
|
|
417
|
-
// Should show activity timeline
|
|
418
|
-
const activityHistory = page.locator('[data-testid="activity-history"], .activity-timeline');
|
|
419
|
-
if (await activityHistory.isVisible()) {
|
|
420
|
-
await expect(activityHistory).toBeVisible();
|
|
421
|
-
|
|
422
|
-
// Should show creation activity
|
|
423
|
-
await expect(page.getByText(/created|submitted/i)).toBeVisible();
|
|
424
|
-
}
|
|
425
|
-
});
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
test.describe("E2E-UF-011: Modify feedback status", () => {
|
|
429
|
-
let helpers: TestHelpers;
|
|
430
|
-
let testDataManager: TestDataManager;
|
|
431
|
-
let organizationId: string;
|
|
432
|
-
let feedbackId: number;
|
|
433
|
-
|
|
434
|
-
test.beforeEach(async ({ page, request }) => {
|
|
435
|
-
helpers = new TestHelpers(page, request);
|
|
436
|
-
|
|
437
|
-
const email = uniqueEmail();
|
|
438
|
-
const name = uniqueName();
|
|
439
|
-
const password = "StrongPass123!";
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
await helpers.registerAndLogin(name, email, password);
|
|
443
|
-
testDataManager = new TestDataManager(page.request);
|
|
444
|
-
|
|
445
|
-
// Get organization ID from registration response (authenticated request context isn't shared)
|
|
446
|
-
organizationId = helpers.getOrganizationId();
|
|
447
|
-
|
|
448
|
-
// Create test feedback
|
|
449
|
-
const feedback = await testDataManager.createFeedback({
|
|
450
|
-
...createTestFeedback({
|
|
451
|
-
title: "Status Test Feedback",
|
|
452
|
-
description: "Testing status changes",
|
|
453
|
-
}),
|
|
454
|
-
organizationId,
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
feedbackId = feedback.feedbackId;
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
test("changes feedback status successfully", async ({ page }) => {
|
|
461
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
462
|
-
|
|
463
|
-
// Select new status
|
|
464
|
-
const statusSelect = page.getByRole("combobox");
|
|
465
|
-
await statusSelect.click();
|
|
466
|
-
await page.getByRole("option", { name: /处理中/ }).click();
|
|
467
|
-
|
|
468
|
-
// Verify status changed
|
|
469
|
-
await expect(statusSelect).toContainText("处理中");
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
test("shows confirmation before status change", async ({ page }) => {
|
|
473
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
474
|
-
|
|
475
|
-
const statusSelect = page.getByRole("combobox");
|
|
476
|
-
await statusSelect.click();
|
|
477
|
-
await page.getByRole("option", { name: /已完成/ }).click();
|
|
478
|
-
|
|
479
|
-
// Might show confirmation dialog
|
|
480
|
-
const confirmDialog = page.locator('[data-testid="confirm-dialog"], .modal');
|
|
481
|
-
if (await confirmDialog.isVisible()) {
|
|
482
|
-
await expect(confirmDialog.getByText(/change status/i)).toBeVisible();
|
|
483
|
-
await page.getByRole("button", { name: /confirm|yes/i }).click();
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// Verify status changed
|
|
487
|
-
await expect(statusSelect).toContainText("已完成");
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
test("syncs status change across list and detail views", async ({ page }) => {
|
|
491
|
-
// Change status in detail view
|
|
492
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
493
|
-
|
|
494
|
-
const statusSelect = page.getByRole("combobox");
|
|
495
|
-
await statusSelect.click();
|
|
496
|
-
await page.getByRole("option", { name: /处理中/ }).click();
|
|
497
|
-
|
|
498
|
-
// Navigate back to list
|
|
499
|
-
await page.goto("/admin/feedback");
|
|
500
|
-
|
|
501
|
-
// Should show updated status in list
|
|
502
|
-
const feedbackItem = page.locator('[data-testid="feedback-item"]:has-text("Status Test Feedback")');
|
|
503
|
-
await expect(feedbackItem.getByText(/处理中/)).toBeVisible();
|
|
504
|
-
|
|
505
|
-
// Go back to detail
|
|
506
|
-
await feedbackItem.click();
|
|
507
|
-
|
|
508
|
-
// Should still show updated status
|
|
509
|
-
await expect(page.getByRole("combobox")).toContainText("处理中");
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
test("requires permission to change status", async ({ page, request }) => {
|
|
513
|
-
// Create a member user without permission
|
|
514
|
-
const memberEmail = uniqueEmail();
|
|
515
|
-
const memberName = uniqueName();
|
|
516
|
-
|
|
517
|
-
await request.post("/api/auth/register", {
|
|
518
|
-
data: {
|
|
519
|
-
name: memberName,
|
|
520
|
-
email: memberEmail,
|
|
521
|
-
password: "TestPass123!",
|
|
522
|
-
},
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
// Invite member to the current organization with a restricted role
|
|
526
|
-
const inviteResponse = await page.request.post(
|
|
527
|
-
`/api/organizations/${organizationId}/invitations`,
|
|
528
|
-
{ data: { email: memberEmail, role: "customer" } },
|
|
529
|
-
);
|
|
530
|
-
expect(inviteResponse.ok()).toBeTruthy();
|
|
531
|
-
const inviteJson = await inviteResponse.json();
|
|
532
|
-
const inviteToken = inviteJson?.data?.token as string | undefined;
|
|
533
|
-
expect(inviteToken).toBeTruthy();
|
|
534
|
-
|
|
535
|
-
// Logout admin
|
|
536
|
-
await page.context().clearCookies();
|
|
537
|
-
|
|
538
|
-
// Login as member
|
|
539
|
-
const memberHelpers = new TestHelpers(page, page.request);
|
|
540
|
-
await memberHelpers.login(memberEmail, "TestPass123!");
|
|
541
|
-
|
|
542
|
-
// Accept invitation to join the admin's organization
|
|
543
|
-
const acceptResponse = await page.request.post("/api/invitations/accept", {
|
|
544
|
-
data: { token: inviteToken },
|
|
545
|
-
});
|
|
546
|
-
expect(acceptResponse.ok()).toBeTruthy();
|
|
547
|
-
|
|
548
|
-
// Ensure org context points to the admin organization
|
|
549
|
-
await page.evaluate((orgId) => {
|
|
550
|
-
document.cookie = `orgId=${orgId};path=/;max-age=2592000;samesite=lax`;
|
|
551
|
-
}, organizationId);
|
|
552
|
-
|
|
553
|
-
// Try to change status
|
|
554
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
555
|
-
|
|
556
|
-
const statusSelect = page.getByRole("combobox");
|
|
557
|
-
|
|
558
|
-
// Should be disabled or not visible
|
|
559
|
-
if (await statusSelect.isVisible()) {
|
|
560
|
-
await expect(statusSelect).toBeDisabled();
|
|
561
|
-
} else {
|
|
562
|
-
await expect(statusSelect).toHaveCount(0);
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
|
-
});
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2026 Echo Team
|
|
3
|
-
*
|
|
4
|
-
* This program is free software: you can redistribute it and/or modify
|
|
5
|
-
* it under the terms of the GNU Affero General Public License as published by
|
|
6
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
-
* (at your option) any later version.
|
|
8
|
-
*
|
|
9
|
-
* This program is distributed in the hope that it will be useful,
|
|
10
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
-
* GNU Affero General Public License for more details.
|
|
13
|
-
*
|
|
14
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { test, expect } from "@playwright/test";
|
|
19
|
-
|
|
20
|
-
function uniqueEmail() {
|
|
21
|
-
return `e2e+${Date.now()}@example.com`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function uniqueTitle() {
|
|
25
|
-
return `E2E feedback ${Date.now()}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function uniqueName() {
|
|
29
|
-
return `E2E User ${Date.now()}`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
test.describe("E2E-UF-001: Submit feedback with basic fields, type, and priority", () => {
|
|
33
|
-
let slug: string;
|
|
34
|
-
const password = "StrongPass123!";
|
|
35
|
-
|
|
36
|
-
test.beforeEach(async ({ page }) => {
|
|
37
|
-
const email = uniqueEmail();
|
|
38
|
-
const name = uniqueName();
|
|
39
|
-
|
|
40
|
-
const register = await page.request.post("/api/auth/register", {
|
|
41
|
-
data: { name, email, password },
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
expect(register.ok()).toBeTruthy();
|
|
45
|
-
const json = await register.json();
|
|
46
|
-
slug = json?.data?.organization?.slug;
|
|
47
|
-
expect(slug).toBeTruthy();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("submits feedback with title, description, type, and priority", async ({
|
|
51
|
-
page,
|
|
52
|
-
}) => {
|
|
53
|
-
const title = uniqueTitle();
|
|
54
|
-
const description = "This is an E2E submission with type and priority.";
|
|
55
|
-
|
|
56
|
-
// Navigate to the portal
|
|
57
|
-
await page.goto(`/${slug}`);
|
|
58
|
-
|
|
59
|
-
// Open feedback form
|
|
60
|
-
await page.getByRole("button", { name: "Submit Feedback" }).click();
|
|
61
|
-
|
|
62
|
-
// Fill basic fields
|
|
63
|
-
await page.getByLabel("Title").fill(title);
|
|
64
|
-
await page.getByLabel("Description").fill(description);
|
|
65
|
-
|
|
66
|
-
// Select feedback type (e.g., Feature Request)
|
|
67
|
-
const typeSelect = page.getByLabel("Type");
|
|
68
|
-
if (await typeSelect.isVisible()) {
|
|
69
|
-
await typeSelect.click();
|
|
70
|
-
await page.getByRole("option", { name: /feature/i }).click();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Select priority (e.g., High)
|
|
74
|
-
const prioritySelect = page.getByLabel("Priority");
|
|
75
|
-
if (await prioritySelect.isVisible()) {
|
|
76
|
-
await prioritySelect.click();
|
|
77
|
-
await page.getByRole("option", { name: /high/i }).click();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Submit the form
|
|
81
|
-
await page.getByRole("button", { name: "Create Post" }).click();
|
|
82
|
-
|
|
83
|
-
// Assert: feedback detail page shows the title
|
|
84
|
-
await expect(page.getByRole("heading", { name: title })).toBeVisible();
|
|
85
|
-
|
|
86
|
-
// Assert: we stay on the portal list page after submit
|
|
87
|
-
const currentUrl = page.url();
|
|
88
|
-
expect(currentUrl).toContain(`/${slug}`);
|
|
89
|
-
|
|
90
|
-
// Navigate to the new feedback detail page via the list
|
|
91
|
-
const listHeading = page.getByRole("heading", { name: title });
|
|
92
|
-
await expect(listHeading).toBeVisible();
|
|
93
|
-
await listHeading.click();
|
|
94
|
-
await expect(
|
|
95
|
-
page.getByRole("heading", { name: title, level: 1 })
|
|
96
|
-
).toBeVisible();
|
|
97
|
-
expect(page.url()).toContain(`/${slug}/feedback/`);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("shows success message after submission", async ({ page }) => {
|
|
101
|
-
const title = uniqueTitle();
|
|
102
|
-
|
|
103
|
-
await page.goto(`/${slug}`);
|
|
104
|
-
await page.getByRole("button", { name: "Submit Feedback" }).click();
|
|
105
|
-
|
|
106
|
-
await page.getByLabel("Title").fill(title);
|
|
107
|
-
await page.getByLabel("Description").fill("Testing success message.");
|
|
108
|
-
await page.getByRole("button", { name: "Create Post" }).click();
|
|
109
|
-
|
|
110
|
-
// Assert: the new feedback appears in the list after reload
|
|
111
|
-
await expect(page.getByRole("heading", { name: title })).toBeVisible();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("new feedback appears in the feedback list", async ({ page }) => {
|
|
115
|
-
const title = uniqueTitle();
|
|
116
|
-
|
|
117
|
-
await page.goto(`/${slug}`);
|
|
118
|
-
await page.getByRole("button", { name: "Submit Feedback" }).click();
|
|
119
|
-
|
|
120
|
-
await page.getByLabel("Title").fill(title);
|
|
121
|
-
await page.getByLabel("Description").fill("Testing list visibility.");
|
|
122
|
-
await page.getByRole("button", { name: "Create Post" }).click();
|
|
123
|
-
|
|
124
|
-
// Wait for navigation to detail or list
|
|
125
|
-
await expect(page.getByRole("heading", { name: title })).toBeVisible();
|
|
126
|
-
|
|
127
|
-
// Navigate back to the portal list
|
|
128
|
-
await page.goto(`/${slug}`);
|
|
129
|
-
|
|
130
|
-
// Assert: the new feedback is visible in the list
|
|
131
|
-
await expect(page.getByText(title)).toBeVisible();
|
|
132
|
-
});
|
|
133
|
-
});
|