Completed studies: finishing a study keeps access after leaving; certificates always shown; no reminders for finished studies; v2.11.0
Publish Container / build (push) Successful in 15m31s
Publish Container / build (push) Successful in 15m31s
- Completion is stored on the user (completedStudySlugs, sticky) when every lesson is done; backfilled at startup - Lessons, notes, progress, quiz, community and certificate stay open for completed studies after leaving the enrollment - Dashboard/My Account list completed studies with Review Lessons and certificate access - Study page shows a completed banner - Lesson-release reminders, weekly digest and re-engagement emails skip finished studies - Fix: streak fields (current/longest/last studied) and firstCompletionAt were dropped on every restart when users were loaded - Tests for completion/access/re-engagement Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7301982bb1
commit
bb83f3e8c5
13 files changed
+221
-52
No files matched your search
@@ -0,0 +1,61 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { state } from '../server/state.js'
|
||||
import {
|
||||
hasStudyAccess,
|
||||
isStudyUserCompleted,
|
||||
getAccessibleStudySlugs,
|
||||
markStudyCompletedIfDone,
|
||||
scheduleReengagementEmails,
|
||||
} from '../server/study-helpers.js'
|
||||
|
||||
function setup() {
|
||||
state.cachedSiteContent = {
|
||||
studies: [{ slug: 'mini', sections: [{ id: 'a' }, { id: 'b' }] }, { slug: 'other', sections: [{ id: 'x' }] }],
|
||||
}
|
||||
}
|
||||
|
||||
test('completing every lesson marks the study completed and keeps access after leaving', () => {
|
||||
setup()
|
||||
const user = { id: 'u1', enrolledStudySlugs: ['mini'] }
|
||||
assert.equal(markStudyCompletedIfDone(user, 'mini', ['a']), false)
|
||||
assert.equal(markStudyCompletedIfDone(user, 'mini', ['a', 'b']), true)
|
||||
assert.ok(isStudyUserCompleted(user, 'mini'))
|
||||
|
||||
user.enrolledStudySlugs = [] // student leaves
|
||||
assert.ok(hasStudyAccess(user, 'mini'))
|
||||
assert.ok(!hasStudyAccess(user, 'other'))
|
||||
assert.deepEqual(getAccessibleStudySlugs(user), ['mini'])
|
||||
})
|
||||
|
||||
test('completion is sticky when a lesson is added later, and not marked twice', () => {
|
||||
setup()
|
||||
const user = { id: 'u2', enrolledStudySlugs: ['mini'] }
|
||||
markStudyCompletedIfDone(user, 'mini', ['a', 'b'])
|
||||
state.cachedSiteContent.studies[0].sections.push({ id: 'c' })
|
||||
assert.equal(markStudyCompletedIfDone(user, 'mini', ['a', 'b']), false)
|
||||
assert.ok(isStudyUserCompleted(user, 'mini'))
|
||||
assert.deepEqual(user.completedStudySlugs, ['mini'])
|
||||
})
|
||||
|
||||
test('unknown or empty studies are never marked completed', () => {
|
||||
setup()
|
||||
const user = { id: 'u3', enrolledStudySlugs: [] }
|
||||
assert.equal(markStudyCompletedIfDone(user, 'nope', ['a']), false)
|
||||
state.cachedSiteContent.studies.push({ slug: 'empty', sections: [] })
|
||||
assert.equal(markStudyCompletedIfDone(user, 'empty', []), false)
|
||||
})
|
||||
|
||||
test('re-engagement emails skip students whose only study is finished', async () => {
|
||||
setup()
|
||||
const sent = []
|
||||
state.studyUsers = [{
|
||||
id: 'u4', username: 'a@b.co', studyRemindersEnabled: true, lastLoginAt: new Date(Date.now() - 40 * 864e5).toISOString(),
|
||||
enrolledStudySlugs: ['mini'], completedStudySlugs: ['mini'], reengagementSentAt: {},
|
||||
}, {
|
||||
id: 'u5', username: 'c@d.co', studyRemindersEnabled: true, lastLoginAt: new Date(Date.now() - 40 * 864e5).toISOString(),
|
||||
enrolledStudySlugs: ['mini'], reengagementSentAt: {},
|
||||
}]
|
||||
await scheduleReengagementEmails(async (email) => { sent.push(email) })
|
||||
assert.deepEqual(sent, ['c@d.co'])
|
||||
})
|
||||
Reference in new issue
Block a user