feat: update tests + seed script, add ecology→board migration
- useOnboarding.test.js: hasEngagedEcology→hasEngagedBoard, /api/ecology/suggestions→/api/board/suggestions, ecology key/route→board in test assertions - onboarding-status.test.js: stale description strings updated - seed-welcome-tester.cjs: communityEcology→board, ecologyPageVisited→boardPageVisited - migrate-ecology-to-board.cjs: one-time migration renames three member fields and activity log action values
This commit is contained in:
parent
49c54764c6
commit
74b2287d48
4 changed files with 95 additions and 26 deletions
69
scripts/migrate-ecology-to-board.cjs
Normal file
69
scripts/migrate-ecology-to-board.cjs
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
/**
|
||||||
|
* One-time migration: rename ecology fields to board across members and activity logs.
|
||||||
|
*
|
||||||
|
* Renames on Member documents:
|
||||||
|
* communityEcology → board
|
||||||
|
* privacy.communityEcology → privacy.board
|
||||||
|
* onboarding.ecologyPageVisited → onboarding.boardPageVisited
|
||||||
|
*
|
||||||
|
* Renames on ActivityLog documents:
|
||||||
|
* action: 'community_ecology_updated' → action: 'board_updated'
|
||||||
|
*
|
||||||
|
* Usage: node scripts/migrate-ecology-to-board.cjs
|
||||||
|
*/
|
||||||
|
require('dotenv').config()
|
||||||
|
const mongoose = require('mongoose')
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const uri = process.env.MONGODB_URI
|
||||||
|
if (!uri) {
|
||||||
|
console.error('MONGODB_URI is not set. Aborting.')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
await mongoose.connect(uri)
|
||||||
|
console.log('Connected to MongoDB.')
|
||||||
|
|
||||||
|
const Member = mongoose.connection.model(
|
||||||
|
'Member',
|
||||||
|
new mongoose.Schema({}, { strict: false, collection: 'members' }),
|
||||||
|
)
|
||||||
|
|
||||||
|
const ActivityLog = mongoose.connection.model(
|
||||||
|
'ActivityLog',
|
||||||
|
new mongoose.Schema({}, { strict: false, collection: 'activitylogs' }),
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Rename member fields ---
|
||||||
|
const memberResult = await Member.updateMany(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
$rename: {
|
||||||
|
communityEcology: 'board',
|
||||||
|
'privacy.communityEcology': 'privacy.board',
|
||||||
|
'onboarding.ecologyPageVisited': 'onboarding.boardPageVisited',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ strict: false },
|
||||||
|
)
|
||||||
|
console.log(
|
||||||
|
`Members updated: ${memberResult.modifiedCount} modified (${memberResult.matchedCount} matched).`,
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Rename activity log action values ---
|
||||||
|
const logResult = await ActivityLog.updateMany(
|
||||||
|
{ action: 'community_ecology_updated' },
|
||||||
|
{ $set: { action: 'board_updated' } },
|
||||||
|
)
|
||||||
|
console.log(
|
||||||
|
`Activity logs updated: ${logResult.modifiedCount} modified (${logResult.matchedCount} matched).`,
|
||||||
|
)
|
||||||
|
|
||||||
|
await mongoose.disconnect()
|
||||||
|
console.log('Done. Disconnected from MongoDB.')
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
* Seed a fresh member in the right state to test the Welcome Workflow.
|
* Seed a fresh member in the right state to test the Welcome Workflow.
|
||||||
* All onboarding flags default to false/null, no craft tags, no ecology topics.
|
* All onboarding flags default to false/null, no craft tags, no board topics.
|
||||||
*
|
*
|
||||||
* Usage: node scripts/seed-welcome-tester.js
|
* Usage: node scripts/seed-welcome-tester.js
|
||||||
* Then pick "Welcome Tester" from the dev login dropdown.
|
* Then pick "Welcome Tester" from the dev login dropdown.
|
||||||
|
|
@ -29,11 +29,11 @@ async function main() {
|
||||||
role: "member",
|
role: "member",
|
||||||
status: "active",
|
status: "active",
|
||||||
craftTags: [],
|
craftTags: [],
|
||||||
communityEcology: { topics: [], offerPeerSupport: false },
|
board: { topics: [], offerPeerSupport: false },
|
||||||
onboarding: {
|
onboarding: {
|
||||||
completedAt: null,
|
completedAt: null,
|
||||||
eventPageVisited: false,
|
eventPageVisited: false,
|
||||||
ecologyPageVisited: false,
|
boardPageVisited: false,
|
||||||
wikiClicked: false,
|
wikiClicked: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ describe('useOnboarding', () => {
|
||||||
goals: {
|
goals: {
|
||||||
hasProfileTags: false,
|
hasProfileTags: false,
|
||||||
hasVisitedEvent: false,
|
hasVisitedEvent: false,
|
||||||
hasEngagedEcology: false,
|
hasEngagedBoard: false,
|
||||||
hasClickedWiki: false,
|
hasClickedWiki: false,
|
||||||
},
|
},
|
||||||
completedAt: null,
|
completedAt: null,
|
||||||
|
|
@ -63,7 +63,7 @@ describe('useOnboarding', () => {
|
||||||
goals: {
|
goals: {
|
||||||
hasProfileTags: true,
|
hasProfileTags: true,
|
||||||
hasVisitedEvent: false,
|
hasVisitedEvent: false,
|
||||||
hasEngagedEcology: true,
|
hasEngagedBoard: true,
|
||||||
hasClickedWiki: false,
|
hasClickedWiki: false,
|
||||||
},
|
},
|
||||||
completedAt: null,
|
completedAt: null,
|
||||||
|
|
@ -81,7 +81,7 @@ describe('useOnboarding', () => {
|
||||||
|
|
||||||
expect(goals.value.hasProfileTags).toBe(true)
|
expect(goals.value.hasProfileTags).toBe(true)
|
||||||
expect(goals.value.hasVisitedEvent).toBe(false)
|
expect(goals.value.hasVisitedEvent).toBe(false)
|
||||||
expect(goals.value.hasEngagedEcology).toBe(true)
|
expect(goals.value.hasEngagedBoard).toBe(true)
|
||||||
expect(goals.value.hasClickedWiki).toBe(false)
|
expect(goals.value.hasClickedWiki).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -93,14 +93,14 @@ describe('useOnboarding', () => {
|
||||||
goals: {
|
goals: {
|
||||||
hasProfileTags: true,
|
hasProfileTags: true,
|
||||||
hasVisitedEvent: true,
|
hasVisitedEvent: true,
|
||||||
hasEngagedEcology: true,
|
hasEngagedBoard: true,
|
||||||
hasClickedWiki: true,
|
hasClickedWiki: true,
|
||||||
},
|
},
|
||||||
completedAt: '2026-04-01T00:00:00Z',
|
completedAt: '2026-04-01T00:00:00Z',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (url === '/api/events/recommended') return Promise.resolve([])
|
if (url === '/api/events/recommended') return Promise.resolve([])
|
||||||
if (url === '/api/ecology/suggestions') return Promise.resolve({ suggestions: [] })
|
if (url === '/api/board/suggestions') return Promise.resolve({ suggestions: [] })
|
||||||
if (url === '/api/wiki/recommended') return Promise.resolve([])
|
if (url === '/api/wiki/recommended') return Promise.resolve([])
|
||||||
return Promise.resolve(null)
|
return Promise.resolve(null)
|
||||||
})
|
})
|
||||||
|
|
@ -123,7 +123,7 @@ describe('useOnboarding', () => {
|
||||||
goals: {
|
goals: {
|
||||||
hasProfileTags: true,
|
hasProfileTags: true,
|
||||||
hasVisitedEvent: true,
|
hasVisitedEvent: true,
|
||||||
hasEngagedEcology: false,
|
hasEngagedBoard: false,
|
||||||
hasClickedWiki: false,
|
hasClickedWiki: false,
|
||||||
},
|
},
|
||||||
completedAt: null,
|
completedAt: null,
|
||||||
|
|
@ -162,7 +162,7 @@ describe('useOnboarding', () => {
|
||||||
goals: {
|
goals: {
|
||||||
hasProfileTags: true,
|
hasProfileTags: true,
|
||||||
hasVisitedEvent: false,
|
hasVisitedEvent: false,
|
||||||
hasEngagedEcology: false,
|
hasEngagedBoard: false,
|
||||||
hasClickedWiki: false,
|
hasClickedWiki: false,
|
||||||
},
|
},
|
||||||
completedAt: null,
|
completedAt: null,
|
||||||
|
|
@ -182,15 +182,15 @@ describe('useOnboarding', () => {
|
||||||
expect(currentSuggestion.value.actionText).toBe('Browse events')
|
expect(currentSuggestion.value.actionText).toBe('Browse events')
|
||||||
})
|
})
|
||||||
|
|
||||||
// 9.6: currentSuggestion returns ecology when tags + event done (priority 3)
|
// 9.6: currentSuggestion returns board when tags + event done (priority 3)
|
||||||
it('9.6: currentSuggestion returns ecology when tags + event done', async () => {
|
it('9.6: currentSuggestion returns board when tags + event done', async () => {
|
||||||
fetchMock.mockImplementation((url) => {
|
fetchMock.mockImplementation((url) => {
|
||||||
if (url === '/api/onboarding/status') {
|
if (url === '/api/onboarding/status') {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
goals: {
|
goals: {
|
||||||
hasProfileTags: true,
|
hasProfileTags: true,
|
||||||
hasVisitedEvent: true,
|
hasVisitedEvent: true,
|
||||||
hasEngagedEcology: false,
|
hasEngagedBoard: false,
|
||||||
hasClickedWiki: false,
|
hasClickedWiki: false,
|
||||||
},
|
},
|
||||||
completedAt: null,
|
completedAt: null,
|
||||||
|
|
@ -205,8 +205,8 @@ describe('useOnboarding', () => {
|
||||||
expect(loading.value).toBe(false)
|
expect(loading.value).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(currentSuggestion.value.key).toBe('ecology')
|
expect(currentSuggestion.value.key).toBe('board')
|
||||||
expect(currentSuggestion.value.action).toBe('/ecology')
|
expect(currentSuggestion.value.action).toBe('/board')
|
||||||
})
|
})
|
||||||
|
|
||||||
// 9.7: currentSuggestion returns wiki when only wiki remaining (priority 4)
|
// 9.7: currentSuggestion returns wiki when only wiki remaining (priority 4)
|
||||||
|
|
@ -217,7 +217,7 @@ describe('useOnboarding', () => {
|
||||||
goals: {
|
goals: {
|
||||||
hasProfileTags: true,
|
hasProfileTags: true,
|
||||||
hasVisitedEvent: true,
|
hasVisitedEvent: true,
|
||||||
hasEngagedEcology: true,
|
hasEngagedBoard: true,
|
||||||
hasClickedWiki: false,
|
hasClickedWiki: false,
|
||||||
},
|
},
|
||||||
completedAt: null,
|
completedAt: null,
|
||||||
|
|
@ -245,14 +245,14 @@ describe('useOnboarding', () => {
|
||||||
goals: {
|
goals: {
|
||||||
hasProfileTags: true,
|
hasProfileTags: true,
|
||||||
hasVisitedEvent: true,
|
hasVisitedEvent: true,
|
||||||
hasEngagedEcology: true,
|
hasEngagedBoard: true,
|
||||||
hasClickedWiki: true,
|
hasClickedWiki: true,
|
||||||
},
|
},
|
||||||
completedAt: '2026-04-01T00:00:00Z',
|
completedAt: '2026-04-01T00:00:00Z',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (url === '/api/events/recommended') return Promise.resolve([])
|
if (url === '/api/events/recommended') return Promise.resolve([])
|
||||||
if (url === '/api/ecology/suggestions') return Promise.resolve({ suggestions: [] })
|
if (url === '/api/board/suggestions') return Promise.resolve({ suggestions: [] })
|
||||||
if (url === '/api/wiki/recommended') return Promise.resolve([])
|
if (url === '/api/wiki/recommended') return Promise.resolve([])
|
||||||
return Promise.resolve(null)
|
return Promise.resolve(null)
|
||||||
})
|
})
|
||||||
|
|
@ -280,7 +280,7 @@ describe('useOnboarding', () => {
|
||||||
goals: {
|
goals: {
|
||||||
hasProfileTags: true,
|
hasProfileTags: true,
|
||||||
hasVisitedEvent: true,
|
hasVisitedEvent: true,
|
||||||
hasEngagedEcology: true,
|
hasEngagedBoard: true,
|
||||||
hasClickedWiki: true,
|
hasClickedWiki: true,
|
||||||
},
|
},
|
||||||
completedAt: '2026-04-01T00:00:00Z',
|
completedAt: '2026-04-01T00:00:00Z',
|
||||||
|
|
@ -289,7 +289,7 @@ describe('useOnboarding', () => {
|
||||||
if (url === '/api/events/recommended') {
|
if (url === '/api/events/recommended') {
|
||||||
return Promise.resolve([{ _id: 'e1', title: 'Game Jam' }])
|
return Promise.resolve([{ _id: 'e1', title: 'Game Jam' }])
|
||||||
}
|
}
|
||||||
if (url === '/api/ecology/suggestions') {
|
if (url === '/api/board/suggestions') {
|
||||||
return Promise.resolve({ suggestions: [{ name: 'Alex' }] })
|
return Promise.resolve({ suggestions: [{ name: 'Alex' }] })
|
||||||
}
|
}
|
||||||
if (url === '/api/wiki/recommended') {
|
if (url === '/api/wiki/recommended') {
|
||||||
|
|
@ -320,7 +320,7 @@ describe('useOnboarding', () => {
|
||||||
goals: {
|
goals: {
|
||||||
hasProfileTags: true,
|
hasProfileTags: true,
|
||||||
hasVisitedEvent: true,
|
hasVisitedEvent: true,
|
||||||
hasEngagedEcology: true,
|
hasEngagedBoard: true,
|
||||||
hasClickedWiki: true,
|
hasClickedWiki: true,
|
||||||
},
|
},
|
||||||
completedAt: '2026-04-01T00:00:00Z',
|
completedAt: '2026-04-01T00:00:00Z',
|
||||||
|
|
@ -329,7 +329,7 @@ describe('useOnboarding', () => {
|
||||||
if (url === '/api/events/recommended') {
|
if (url === '/api/events/recommended') {
|
||||||
return Promise.resolve([{ _id: 'e1', title: 'Game Jam' }])
|
return Promise.resolve([{ _id: 'e1', title: 'Game Jam' }])
|
||||||
}
|
}
|
||||||
if (url === '/api/ecology/suggestions') {
|
if (url === '/api/board/suggestions') {
|
||||||
return Promise.resolve({ suggestions: [] })
|
return Promise.resolve({ suggestions: [] })
|
||||||
}
|
}
|
||||||
if (url === '/api/wiki/recommended') {
|
if (url === '/api/wiki/recommended') {
|
||||||
|
|
@ -366,14 +366,14 @@ describe('useOnboarding', () => {
|
||||||
goals: {
|
goals: {
|
||||||
hasProfileTags: true,
|
hasProfileTags: true,
|
||||||
hasVisitedEvent: true,
|
hasVisitedEvent: true,
|
||||||
hasEngagedEcology: true,
|
hasEngagedBoard: true,
|
||||||
hasClickedWiki: true,
|
hasClickedWiki: true,
|
||||||
},
|
},
|
||||||
completedAt: '2026-04-01T00:00:00Z',
|
completedAt: '2026-04-01T00:00:00Z',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (url === '/api/events/recommended') return Promise.resolve([])
|
if (url === '/api/events/recommended') return Promise.resolve([])
|
||||||
if (url === '/api/ecology/suggestions') return Promise.resolve({ suggestions: [] })
|
if (url === '/api/board/suggestions') return Promise.resolve({ suggestions: [] })
|
||||||
if (url === '/api/wiki/recommended') return Promise.resolve([])
|
if (url === '/api/wiki/recommended') return Promise.resolve([])
|
||||||
return Promise.resolve(null)
|
return Promise.resolve(null)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ describe('GET /api/onboarding/status', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// 1.2: hasProfileTags true when both tag types present
|
// 1.2: hasProfileTags true when both tag types present
|
||||||
it('hasProfileTags is true when member has both craft tags and ecology topics', async () => {
|
it('hasProfileTags is true when member has both craft tags and board topics', async () => {
|
||||||
requireAuth.mockResolvedValue({
|
requireAuth.mockResolvedValue({
|
||||||
_id: 'member-1',
|
_id: 'member-1',
|
||||||
craftTags: ['game-design'],
|
craftTags: ['game-design'],
|
||||||
|
|
@ -68,7 +68,7 @@ describe('GET /api/onboarding/status', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// 1.3: hasProfileTags false when only craft tags
|
// 1.3: hasProfileTags false when only craft tags
|
||||||
it('hasProfileTags is false when member has craft tags but no ecology topics', async () => {
|
it('hasProfileTags is false when member has craft tags but no board topics', async () => {
|
||||||
requireAuth.mockResolvedValue({
|
requireAuth.mockResolvedValue({
|
||||||
_id: 'member-1',
|
_id: 'member-1',
|
||||||
craftTags: ['game-design'],
|
craftTags: ['game-design'],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue