Files
safavimohadeseh78andCursor c53a67831d
Frontend CI / unit (test, type-check, build) (push) Successful in 7m21s
Frontend CI / browser E2E (frontend + pinned backend) (push) Successful in 11m38s
feat: complete the thirteen-stage learning experience and E2E journey
Play levels 7-13 in the app and pin E2E to the local thirteen-stage backend commit.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 19:42:04 +03:30

97 lines
3.4 KiB
TypeScript

import assert from 'node:assert/strict'
import test from 'node:test'
import { ApiNetworkError, StaleSessionError } from '../src/api/errors'
import { invalidateSession } from '../src/api/authSession'
import { bindMeMutationApply, resetMeMutationQueueForTests, runMeMutation } from '../src/api/meMutationQueue'
import {
adjustFund,
canIncreaseFund,
changeLabel,
emptyFunds,
fundSum,
previewScenarios,
remainingCoins,
} from '../src/modules/gold-land/portfolio/portfolioMath'
import { setRiskAnswer } from '../src/modules/gold-land/risk-return/riskChoices'
const scenarios = [
{
id: 'sunny',
title: 'هوای آفتابی',
note: 'مثال',
changes: [
{ fundId: 'sun' as const, percent: 20 },
{ fundId: 'rain' as const, percent: -10 },
{ fundId: 'wind' as const, percent: 0 },
],
},
{
id: 'drop',
title: 'افت همگانی',
note: 'مثال',
changes: [
{ fundId: 'sun' as const, percent: -10 },
{ fundId: 'rain' as const, percent: -10 },
{ fundId: 'wind' as const, percent: -10 },
],
},
]
const funds = [
{ id: 'sun' as const, label: 'آفتاب' },
{ id: 'rain' as const, label: 'باران' },
{ id: 'wind' as const, label: 'باد' },
]
test('allocation stays on step 10 and cannot pass 100 or go negative', () => {
let amounts = emptyFunds()
assert.equal(remainingCoins(100, amounts), 100)
assert.equal(canIncreaseFund(amounts, 10, 100), true)
for (let index = 0; index < 4; index += 1) amounts = adjustFund(amounts, 'sun', 10, 10, 100)
amounts = adjustFund(amounts, 'rain', 10, 10, 100)
amounts = adjustFund(amounts, 'wind', 50, 10, 100)
assert.equal(fundSum(amounts), 100)
assert.equal(remainingCoins(100, amounts), 0)
assert.deepEqual(adjustFund(amounts, 'sun', 10, 10, 100), amounts)
assert.deepEqual(adjustFund(amounts, 'rain', -20, 10, 100), amounts)
const reduced = adjustFund(amounts, 'rain', -10, 10, 100)
assert.equal(reduced.rain, 0)
assert.equal(remainingCoins(100, reduced), 10)
})
test('changing the mix refreshes all shown outcomes and loss is written in words', () => {
const first = previewScenarios({ sun: 100, rain: 0, wind: 0 }, scenarios, funds)
assert.equal(first[0]?.changeLabel, 'سود 20 سکه')
const drop = first.find((item) => item.id === 'drop')
assert.equal(drop?.total, 90)
assert.equal(drop?.changeLabel, changeLabel(90))
assert.equal(drop?.changeLabel.includes('زیان'), true)
const second = previewScenarios({ sun: 40, rain: 30, wind: 30 }, scenarios, funds)
assert.equal(second[0]?.total, 105)
assert.notEqual(second[0]?.total, first[0]?.total)
})
test('review keeps answers and a network or stale session does not apply me', async () => {
let answers = setRiskAnswer({}, 'q-one', 'safe')
answers = setRiskAnswer(answers, 'q-one', 'depend')
assert.equal(answers['q-one'], 'depend')
const mix = { sun: 40, rain: 30, wind: 30 }
resetMeMutationQueueForTests()
let applied = 0
bindMeMutationApply(() => {
applied += 1
return true
})
await assert.rejects(() => runMeMutation(async () => {
throw new ApiNetworkError()
}), ApiNetworkError)
assert.equal(applied, 0)
assert.equal(mix.sun, 40)
assert.equal(answers['q-one'], 'depend')
await assert.rejects(() => runMeMutation(async () => {
invalidateSession()
return { me: { user: { coins: 1 } } }
}), StaleSessionError)
assert.equal(applied, 0)
assert.equal(answers['q-one'], 'depend')
})