ghostguild-org/e2e/wave-slack-onboarding.spec.js
Jennie Robinson Faber d15458b30a
Some checks failed
Test / vitest (push) Successful in 12m6s
Test / playwright (push) Failing after 9m39s
Test / visual (push) Failing after 9m28s
Test / Notify on failure (push) Successful in 2s
chore(slack): remove dead invite path, archive checkSlackJoins poller
Wave-based onboarding makes the auto-invite + polling path obsolete.

- Removes SlackService.inviteUserToSlack — admins now send invites
  through Slack's UI and flip the flag in our admin endpoint.
- Removes the slack_invite_failed admin alert + its detector. The
  alert no longer has a meaningful trigger (we don't attempt invites).
- Archives server/utils/checkSlackJoins.js (and its test) under
  _archive/ in case the polling pattern is needed again post-pilot.
- Deletes the Nitro plugin that scheduled checkSlackJoins on boot
  + hourly. Nothing in nitro.config / nuxt.config / package.json
  registered it elsewhere.
- Drops the slack_invite_failed branch from adminAlerts.test; the
  enum slug stays in adminAlertDismissal so historical dismissal
  rows continue to validate.

notifyNewMember (vetting-channel notification) and findUserByEmail
(used by the auto-flag helper) are retained.
2026-04-29 12:34:21 +01:00

103 lines
4.4 KiB
JavaScript

// Spec: docs/specs/wave-based-slack-onboarding.md
// Test plan: docs/specs/wave-based-slack-onboarding-tests.md §6 + §7
//
// SCAFFOLD: every test is `.skip`ed and contains a TODO. As the UI lands,
// unskip and fill in selectors / fixtures.
//
// These cover the rendered behavior that unit tests can't: dashboard line
// visibility under different member statuses, and the admin-list "Mark as
// Slack invited" button + status display.
import { test, expect } from './helpers/fixtures.js'
test.describe('Member dashboard — Slack-coming note (§7)', () => {
test.skip('shows note for active member without Slack (7.1)', async () => {
// TODO: seed a member { status: 'active', slackInvited: false }, sign in,
// navigate to /member/dashboard, assert the one-liner is visible:
// await expect(page.getByText(/within 2.3 weeks/i)).toBeVisible()
})
test.skip('hides note once slackInvited:true (7.2)', async () => {
// TODO: same as 7.1 but with slackInvited:true; assert text not present.
})
test.skip('hides note for pending_payment member (7.3)', async () => {
// TODO: pending_payment + slackInvited:false; assert text not present.
})
test.skip('hides note for suspended/cancelled/guest (7.4)', async () => {
// TODO: parameterize across statuses { suspended, cancelled, guest }.
})
test.skip('copy contains no wave/cohort/batch language (7.5)', async ({ adminPage }) => {
await adminPage.goto('/member/dashboard')
const html = await adminPage.content()
expect(html).not.toMatch(/\bwave\b/i)
expect(html).not.toMatch(/\bcohort\b/i)
expect(html).not.toMatch(/\bbatch\b/i)
})
test.skip('renders as plain text — no banner / modal / callout styling (7.6)', async () => {
// TODO: assert the note's container is not a UAlert / modal / heavy callout
// (e.g. no .alert, no role="dialog" wrapper).
})
test.skip('SSR renders without auth — note absent (7.7)', async ({ browser }) => {
const context = await browser.newContext()
const page = await context.newPage()
const response = await page.goto('/member/dashboard')
const ssrHtml = await response.text()
expect(ssrHtml).not.toMatch(/within 2.3 weeks/i)
await context.close()
})
test.skip('copy matches approved wording (7.8)', async () => {
// TODO: replace with the final approved string once the Open Question is resolved.
})
})
test.describe('Admin members — Slack-invited control (§6)', () => {
test.skip('shows "Mark as Slack invited" for slackInvited:false (6.1)', async ({ adminPage }) => {
await adminPage.goto('/admin/members')
// TODO: locate a row for a member with slackInvited:false and assert the
// button is visible.
// await expect(adminPage.getByRole('button', { name: /Mark as Slack invited/i })).toBeVisible()
})
test.skip('replaces button with "Invited <date>" once flipped (6.2)', async () => {
// TODO: click the button on a row; assert button is gone, date string visible.
})
test.skip('click triggers single PATCH and updates row in place (6.4)', async ({ adminPage }) => {
// TODO: spy on network for /api/admin/members/*/slack-status; click button;
// assert single PATCH, success, no full-page reload.
})
test.skip('status labels read "Not yet invited" / "Invited" — not "Pending" (6.5)', async ({ adminPage }) => {
await adminPage.goto('/admin/members')
// TODO:
// await expect(adminPage.getByText(/Not yet invited/i).first()).toBeVisible()
// const html = await adminPage.content()
// expect(html).not.toMatch(/Slack:\s*Pending/i)
})
test.skip('member detail page mirrors list controls (6.6)', async () => {
// TODO: navigate to /admin/members/<id>; assert button + date display.
})
test.skip('no UI references slackInviteStatus (6.7)', async ({ adminPage }) => {
// Static assertion of rendered HTML — no leftover badge labels keyed off the dropped field.
await adminPage.goto('/admin/members')
const html = await adminPage.content()
expect(html).not.toMatch(/slackInviteStatus/)
})
test.skip('UI rolls back on PATCH error — no false "Invited" badge (6.8)', async () => {
// TODO: mock the endpoint to return 500; assert the row stays in
// "Not yet invited" state.
})
test.skip('proposed: sortable on slackInvitedAt + filter "no Slack yet" (6.9)', async () => {
// TODO: dependent on Open Question — wire up if implemented.
})
})