Skip to content

Commit a13e715

Browse files
committed
feat(demo): fix transaction explorer links and enhance score tracking
- Fix hardcoded stellar.expert URLs to dynamically detect testnet/mainnet - Update HelloMilestoneDemo, EscrowInitializer, and EscrowDisplay components - Add comprehensive demo completion history tracking system - Create useDemoCompletionHistory hook for persistent score storage - Add DemoCompletionHistory component with expandable history view - Track completion time, cumulative points, best scores, and averages - Show completion badges and detailed statistics - Install date-fns for better date formatting - Add comprehensive test coverage for completion history functionality
1 parent f3b42e6 commit a13e715

File tree

8 files changed

+640
-24
lines changed

8 files changed

+640
-24
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { renderHook, act } from '@testing-library/react';
2+
import { useDemoCompletionHistory } from '../../hooks/useDemoCompletionHistory';
3+
4+
// Mock the contexts
5+
jest.mock('../../contexts/WalletContext', () => ({
6+
useGlobalWallet: () => ({
7+
walletData: {
8+
publicKey: 'test-wallet-address',
9+
network: 'TESTNET',
10+
isMainnet: false,
11+
},
12+
isConnected: true,
13+
}),
14+
}));
15+
16+
jest.mock('../../contexts/ToastContext', () => ({
17+
useToast: () => ({
18+
addToast: jest.fn(),
19+
}),
20+
}));
21+
22+
// Mock localStorage
23+
const localStorageMock = {
24+
getItem: jest.fn(),
25+
setItem: jest.fn(),
26+
removeItem: jest.fn(),
27+
clear: jest.fn(),
28+
};
29+
Object.defineProperty(window, 'localStorage', {
30+
value: localStorageMock,
31+
});
32+
33+
describe('useDemoCompletionHistory', () => {
34+
beforeEach(() => {
35+
jest.clearAllMocks();
36+
localStorageMock.getItem.mockReturnValue(null);
37+
});
38+
39+
it('should initialize with empty history', () => {
40+
const { result } = renderHook(() => useDemoCompletionHistory());
41+
42+
expect(result.current.completionHistory).toEqual([]);
43+
expect(result.current.isLoading).toBe(false);
44+
expect(result.current.error).toBeNull();
45+
});
46+
47+
it('should add completion record', () => {
48+
const { result } = renderHook(() => useDemoCompletionHistory());
49+
50+
act(() => {
51+
result.current.addCompletion({
52+
demoId: 'hello-milestone',
53+
demoName: 'Baby Steps to Riches',
54+
score: 85,
55+
pointsEarned: 85,
56+
completionTime: 120,
57+
isFirstCompletion: true,
58+
});
59+
});
60+
61+
expect(result.current.completionHistory).toHaveLength(1);
62+
expect(result.current.completionHistory[0]).toMatchObject({
63+
demoId: 'hello-milestone',
64+
demoName: 'Baby Steps to Riches',
65+
score: 85,
66+
pointsEarned: 85,
67+
completionTime: 120,
68+
isFirstCompletion: true,
69+
});
70+
});
71+
72+
it('should calculate demo statistics correctly', () => {
73+
const { result } = renderHook(() => useDemoCompletionHistory());
74+
75+
// Add multiple completions
76+
act(() => {
77+
result.current.addCompletion({
78+
demoId: 'hello-milestone',
79+
demoName: 'Baby Steps to Riches',
80+
score: 80,
81+
pointsEarned: 80,
82+
completionTime: 120,
83+
isFirstCompletion: true,
84+
});
85+
});
86+
87+
act(() => {
88+
result.current.addCompletion({
89+
demoId: 'hello-milestone',
90+
demoName: 'Baby Steps to Riches',
91+
score: 90,
92+
pointsEarned: 20, // 25% of original for replay
93+
completionTime: 100,
94+
isFirstCompletion: false,
95+
});
96+
});
97+
98+
expect(result.current.getCompletionCount('hello-milestone')).toBe(2);
99+
expect(result.current.getTotalPointsEarned('hello-milestone')).toBe(100);
100+
expect(result.current.getBestScore('hello-milestone')).toBe(90);
101+
expect(result.current.getAverageScore('hello-milestone')).toBe(85);
102+
});
103+
104+
it('should filter demo history by demo ID', () => {
105+
const { result } = renderHook(() => useDemoCompletionHistory());
106+
107+
act(() => {
108+
result.current.addCompletion({
109+
demoId: 'hello-milestone',
110+
demoName: 'Baby Steps to Riches',
111+
score: 85,
112+
pointsEarned: 85,
113+
completionTime: 120,
114+
isFirstCompletion: true,
115+
});
116+
});
117+
118+
act(() => {
119+
result.current.addCompletion({
120+
demoId: 'other-demo',
121+
demoName: 'Other Demo',
122+
score: 90,
123+
pointsEarned: 90,
124+
completionTime: 100,
125+
isFirstCompletion: true,
126+
});
127+
});
128+
129+
const helloMilestoneHistory = result.current.getDemoHistory('hello-milestone');
130+
expect(helloMilestoneHistory).toHaveLength(1);
131+
expect(helloMilestoneHistory[0].demoId).toBe('hello-milestone');
132+
});
133+
});

components/EscrowDisplay.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import { useEscrowContext } from '@/contexts/EscrowContext';
44
import { MultiReleaseEscrow } from '@/contexts/EscrowContext';
5+
import { useGlobalWallet } from '@/contexts/WalletContext';
56

67
export const EscrowDisplay = () => {
78
const { escrowData } = useEscrowContext();
9+
const { walletData } = useGlobalWallet();
810

911
if (!escrowData) {
1012
return null;
@@ -175,7 +177,11 @@ export const EscrowDisplay = () => {
175177
{/* Stellar Viewer Link */}
176178
<div className='mt-6 text-center'>
177179
<a
178-
href={`https://stellar.expert/explorer/testnet/contract/${escrowData.contractId}`}
180+
href={(() => {
181+
const isTestnet = walletData?.network === 'TESTNET' || !walletData?.isMainnet;
182+
const networkSuffix = isTestnet ? 'testnet' : 'public';
183+
return `https://stellar.expert/explorer/${networkSuffix}/contract/${escrowData.contractId}`;
184+
})()}
179185
target='_blank'
180186
rel='noopener noreferrer'
181187
className='inline-flex items-center bg-blue-600 text-white px-6 py-3 rounded-md hover:bg-blue-700 transition-colors'

components/EscrowInitializer.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,10 @@ export const EscrowInitializer = () => {
101101
};
102102

103103
const getStellarViewerUrl = (contractId: string) => {
104-
return `https://stellar.expert/explorer/testnet/contract/${contractId}`;
104+
// Determine network-specific URL based on wallet data
105+
const isTestnet = walletData?.network === 'TESTNET' || !walletData?.isMainnet;
106+
const networkSuffix = isTestnet ? 'testnet' : 'public';
107+
return `https://stellar.expert/explorer/${networkSuffix}/contract/${contractId}`;
105108
};
106109

107110
if (!isConnected) {

0 commit comments

Comments
 (0)