Files
sky_phone/frontend/src/views/apps/CryptoApp.contract.test.ts
T
smx.pusha 6def666e1b FIX - align VaultX activity filters
Center every activity filter icon and label on the segmented control axis so differing SVG baselines no longer shift the visual height. Keep the alignment covered by the VaultX contract test.
2026-08-18 12:53:52 +02:00

296 lines
12 KiB
TypeScript

import { readFileSync } from 'node:fs'
import { describe, expect, it } from 'vitest'
const source = readFileSync(new URL('./CryptoApp.vue', import.meta.url), 'utf8')
const logoSource = readFileSync(
new URL('../../components/crypto/CryptoLogo.vue', import.meta.url),
'utf8',
)
const server = readFileSync(
new URL('../../../../sky_phone/source/server/crypto.lua', import.meta.url),
'utf8',
)
const passwordProvider = readFileSync(
new URL(
'../../../../sky_phone/source/server/crypto_password.js',
import.meta.url,
),
'utf8',
)
const config = readFileSync(
new URL('../../../../sky_phone/config/config.lua', import.meta.url),
'utf8',
)
const testServer = readFileSync(
new URL('../../../testserver/index.cjs', import.meta.url),
'utf8',
)
describe('VaultX crypto app contracts', () => {
it('uses Sky UI without introducing Konsta components', () => {
expect(source).not.toContain("from 'konsta/vue'")
for (const component of [
'SkyAppPage',
'SkyNavbar',
'SkyScrollArea',
'SkyField',
'SkyButton',
'SkySheet',
'SkyPillNavigation',
'SkySegmented',
]) {
expect(source).toContain(`<${component}`)
}
})
it('shows distinct icons in every primary navigation item', () => {
expect(source).toContain('<WalletCards')
expect(source).toContain('<ChartNoAxesCombined')
expect(source).toContain('<History')
expect(source).toContain('<UserRound')
expect(source).toMatch(
/\.sky-pill-navigation \.sky-segmented-button--active\)\s*\{\s*color:\s*#fff;/,
)
})
it('includes advanced market detail and persistent profile controls', () => {
expect(source).toContain('selected.priceHistory')
expect(source).toContain('const detailChart = computed')
expect(source).toContain('CHART_PERIOD_CONFIG[period.value]')
expect(source).toContain('class="detail-chart__marker"')
expect(source).toContain('v-for="tick in detailChart.yTicks"')
expect(source).toContain('v-for="value in CHART_PERIODS"')
expect(source).toContain(':aria-pressed="period === value"')
expect(source).toContain("t('marketDetail.statistics')")
expect(source).toContain('class="detail-trade-dock"')
expect(source).toContain('class="vault-view vault-detail-scroll"')
expect(source).toContain('class="trade-side-selector"')
expect(source).toContain('class="trade-entry-card"')
expect(source).toContain('class="trade-protection"')
expect(source).toContain('crypto.pendingQuote = null')
expect(source).toContain('<SkyToggle')
expect(server).toContain('sky_phone:crypto:update-profile')
expect(server).toContain('`price_alerts`')
})
it('supports secure profile editing and header sign out', () => {
expect(source).toContain('class="profile-signout"')
expect(source).toContain("<span>{{ t('logout') }}</span>")
expect(source).not.toMatch(/icon-only\s+class="profile-signout"/)
expect(source).toContain('class="profile-edit-button"')
expect(source).toContain("sheet.value = 'profile'")
expect(source).toContain('v-model="profileCurrentPassword"')
expect(source).toContain('v-model="profileNewPassword"')
expect(server).toContain(
'verify_password(profile.id, data.currentPassword)',
)
expect(server).toContain('CryptoHashPassword(new_password)')
})
it('provides detailed registration and server-authoritative crypto-key transfers', () => {
const transferServer = server.slice(
server.indexOf('local function execute_transfer'),
server.indexOf('Bridge.Callbacks.Register("sky_phone:crypto:quote"'),
)
expect(source).toContain('class="password-strength"')
expect(source).toContain('v-model="confirmPassword"')
expect(source).toContain('<SkyCheckbox')
expect(source).toContain("sheet.value = 'send'")
expect(source).toContain('v-model="transferWalletKey"')
expect(source).toContain('profile?.walletKey')
expect(server).toContain('`crypto_key` CHAR(22)')
expect(server).toContain(
'Bridge.Callbacks.Register("sky_phone:crypto:recipient"',
)
expect(server).toContain(
'Bridge.Callbacks.Register("sky_phone:crypto:transfer"',
)
expect(server).toContain("'transfer_out'")
expect(server).toContain("'transfer_in'")
expect(server).toContain('Bridge.Database.Transaction(queries)')
expect(server).toContain('verify_password(profile.id, data.password)')
expect(transferServer).not.toContain('"CASH"')
expect(transferServer).not.toContain('Bridge.Framework.AddMoney')
expect(transferServer).not.toContain('Bridge.Framework.RemoveMoney')
})
it('copies and shares the public account key through phone-owned flows', () => {
expect(source).toContain("import { copyText } from '@/utils/clipboard'")
expect(source).toContain(
"import { useEasyShareStore } from '@/stores/easyshare'",
)
expect(source).toContain('class="wallet-key-card__actions"')
expect(source).toContain('@click="copyWalletKey"')
expect(source).toContain('@click="shareWalletKey"')
expect(source).toContain("appId: 'crypto'")
expect(source).toContain("kind: 'text'")
})
it('uses the premium dashboard hierarchy without manual refresh controls', () => {
expect(source).toContain('class="portfolio-shell"')
expect(source).toContain('class="featured-market"')
expect(source).toContain('class="activity-status"')
expect(source).toContain('class="profile-card"')
expect(source).not.toContain('RefreshCw')
expect(source).not.toContain(':aria-label="t(\'refresh\')"')
})
it('keeps portfolio quick actions concise and interactive', () => {
const quickActions = source.slice(
source.indexOf('<div class="quick">'),
source.indexOf('<SkyCard class="allocation-card">'),
)
expect(quickActions).not.toContain('<small>')
expect(source).toContain('@media (hover: hover) and (pointer: fine)')
expect(source).toContain('.quick button:hover')
expect(source).toContain('.quick button:focus-visible')
})
it('anchors portfolio performance history and forecast at the current position', () => {
expect(source).toContain('const portfolioProfitLoss = computed')
expect(source).toContain('class="portfolio-history"')
expect(source).toContain('class="portfolio-forecast"')
expect(source).toContain(':cx="portfolioChart.currentX"')
expect(source).toContain(':cy="portfolioChart.currentY"')
expect(source).toContain("t('portfolio.current')")
expect(source).toContain('stroke-dasharray: 7 7')
})
it('uses the compact Flare-style navigation and polished overlay transitions', () => {
const navbarTag = source.match(/<SkyNavbar[\s\S]*?>/)?.[0] ?? ''
expect(source).toContain('class="vault-navbar"')
expect(source).toContain('class="vault-header-logo"')
expect(source).toContain(
'<CryptoLogo class="vault-detail-logo" :market="detail" />',
)
expect(source).toContain('.vault-detail-title > span:last-child')
expect(source).not.toMatch(/\.vault-detail-title > span\s*\{/)
expect(source).not.toContain('vault-header-logo--detail')
expect(source).not.toContain('class="coin-detail-logo"')
expect(source).not.toContain('.vault-header-brand > i')
expect(source).toContain(':show-back="Boolean(detail)"')
expect(navbarTag).not.toMatch(/\slarge(?:\s|=|>)/)
expect(source).toContain('class="vault-view"')
expect(source).toContain('@keyframes vault-view-in')
expect(source).toContain('class="sheet-header"')
expect(source).toContain('class="sheet-close"')
expect(source).toContain('v-if="sheet !== \'profile\'"')
expect(source).toContain('class="sheet-market-summary"')
})
it('gives profile editing a full-width field and save treatment', () => {
expect(source).toContain("'sheet--profile': sheet === 'profile'")
expect(source).toContain('class="profile-edit-fields"')
expect(source.match(/class="profile-edit-field"/g)).toHaveLength(3)
expect(source).toContain('autocomplete="current-password"')
expect(source).toContain('autocomplete="new-password"')
expect(source).toContain('class="profile-save-button"')
expect(source).toContain('.profile-edit-field :deep(.sky-field__border)')
})
it('keeps app typography readable on the scaled phone surface', () => {
expect(source).not.toMatch(/font-size:\s*[6-8]px/)
expect(source).toContain(':content-wrap="false"')
})
it('groups the activity summary and icon filters into one VaultX hub', () => {
expect(source).toContain('class="activity-overview"')
expect(source).toContain('class="activity-filter-panel"')
expect(source).toContain('class="activity-filters"')
expect(source).toContain('class="activity-status__icon"')
expect(source).toContain('<WalletCards :size="15" />')
expect(source).toContain('top: 50%;')
expect(source).toContain('transform: translateY(-50%);')
})
it('exposes a broad fictional market with dedicated logo marks', () => {
const cryptoConfig = config.slice(
config.indexOf('Config.Crypto = {'),
config.indexOf('-- Server-only configuration'),
)
expect(cryptoConfig.match(/\n\s+Id = "/g)).toHaveLength(24)
expect(cryptoConfig.match(/\n\s+Logo = "/g)).toHaveLength(24)
expect(server).toContain('logo = config.Logo')
expect(source).toContain('<CryptoLogo')
expect(logoSource.match(/^ [a-z]+: \[/gm)).toHaveLength(24)
for (const mark of [
'ember',
'crown',
'orbit',
'quantum',
'moss',
'flux',
'tide',
'shard',
'pixel',
]) {
expect(logoSource).toContain(` ${mark}: [`)
}
})
it('keeps all consequential calculations and state transitions on the server', () => {
expect(server).toContain(
'Bridge.Callbacks.Register("sky_phone:crypto:quote"',
)
expect(server).toContain(
'Bridge.Callbacks.Register("sky_phone:crypto:execute"',
)
expect(server).toContain('`consumed_operation_id`')
expect(server).toContain('`idempotency_key`')
expect(server).toContain("`status` = 'manual_review'")
expect(server).toContain('Bridge.Framework.RemoveMoney')
expect(server).toContain('Bridge.Framework.AddMoney')
expect(server).toContain('local function with_exchange_lock')
expect(server).toContain('local function reconcile_settlements')
expect(server).toContain('settlement_ledger_queries')
})
it('streams server-driven market movement into live portfolio values', () => {
expect(config).toContain('PriceTickMinimumSeconds')
expect(config).toContain('MarketsPerTickMaximum')
expect(config).toContain('TickMovementDivisor')
expect(config).toContain('CycleDurationMinimumTicks')
expect(config).toContain('CycleStrengthMaximumBasisPoints')
expect(config).toContain('GlobalCycleMinimumTicks')
expect(config).toContain('MarketShockChanceBasisPoints')
expect(server).toContain('local market_dynamics = {}')
expect(server).toContain('global_market_trend')
expect(server).toContain('local function advance_market_cycle')
expect(server).toContain(
'dynamics.cycle_direction = -dynamics.cycle_direction',
)
expect(server).toContain('local function advance_global_market_cycle')
expect(server).toContain('Config.Crypto.MeanReversionBasisPoints')
expect(server).toContain('TriggerClientEvent("sky_phone:crypto:changed"')
expect(server).toContain('priceHistory = price_history')
expect(source).toContain('selected.priceHistory')
expect(source).toContain('4500 + Math.random() * 2500')
expect(testServer).toContain('const cryptoMarketDynamics = new Map()')
expect(testServer).toContain('function advanceCryptoCycle(')
})
it('stores cash in price-scale minor units throughout the ledger', () => {
expect(server).toContain(
'local ledger_amount = amount * Config.Crypto.PriceScale',
)
expect(server).toContain(
'Config.Crypto.TreasuryCash * Config.Crypto.PriceScale',
)
expect(server).toContain(
'amount = decimal_string(row.amount, Config.Crypto.PriceScale)',
)
})
it('uses a memory-hard password provider with constant-time verification', () => {
expect(passwordProvider).toContain('scryptSync')
expect(passwordProvider).toContain('timingSafeEqual')
expect(passwordProvider).toContain('randomBytes(16)')
expect(server).not.toMatch(/data\.price\b/)
expect(server).not.toMatch(/data\.fee\b/)
})
})