diff --git a/frontend/coverage/ContributionFlow.tsx.html b/frontend/coverage/ContributionFlow.tsx.html new file mode 100644 index 00000000..48d6c8e2 --- /dev/null +++ b/frontend/coverage/ContributionFlow.tsx.html @@ -0,0 +1,1099 @@ + + + + + + Code coverage report for ContributionFlow.tsx + + + + + + + + + +
+
+

All files ContributionFlow.tsx

+
+ +
+ 100% + Statements + 226/226 +
+ + +
+ 95.58% + Branches + 65/68 +
+ + +
+ 100% + Functions + 12/12 +
+ + +
+ 100% + Lines + 226/226 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +3391x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +23x +23x +23x +23x +23x +23x +16x +16x +  +  +  +13x +  +13x +13x +  +7x +13x +7x +7x +  +  +  +1x +1x +1x +1x +1x +1x +1x +1x +  +1x +1x +1x +1x +1x +1x +1x +  +  +  +  +  +  +  +  +  +  +  +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +  +118x +118x +118x +118x +118x +118x +118x +118x +  +118x +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +118x +  +118x +118x +  +118x +23x +23x +23x +16x +16x +23x +  +118x +13x +13x +13x +13x +13x +13x +13x +7x +7x +7x +7x +7x +13x +13x +6x +6x +6x +6x +6x +13x +  +118x +2x +2x +2x +2x +  +118x +2x +2x +2x +2x +2x +2x +2x +  +118x +  +118x +118x +  +118x +2x +  +2x +  +  +  +118x +33x +33x +33x +33x +33x +6x +27x +7x +20x +  +  +33x +33x +33x +7x +7x +7x +7x +7x +7x +  +7x +  +33x +33x +  +  +  +118x +111x +111x +111x +111x +111x +111x +111x +111x +111x +111x +111x +111x +111x +111x +  +  +111x +80x +80x +240x +240x +240x +240x +240x +240x +240x +240x +240x +240x +80x +80x +  +  +111x +111x +111x +111x +111x +  +111x +20x +20x +  +20x +91x +111x +111x +6x +  +111x +111x +111x +  +  +  +118x +7x +7x +7x +7x +7x +7x +7x +  +  +  +118x +118x +118x +118x +118x +118x +118x +  +  +118x +118x +118x +118x +118x +118x +118x +118x +  +118x +  +1x + 
import { useState } from 'react';
+import {
+  Box,
+  Stack,
+  Typography,
+  TextField,
+  Dialog,
+  DialogTitle,
+  DialogContent,
+  DialogActions,
+  Alert,
+  CircularProgress,
+  Divider,
+  Chip,
+} from '@mui/material';
+import { Button } from './Button';
+import { ContributionSuccessModal } from './ContributionSuccessModal';
+import type { TransactionStatus } from '../types/contribution';
+ 
+// ── Types ────────────────────────────────────────────────────────────────────
+ 
+export interface ContributionFlowProps {
+  /** Minimum allowed contribution in XLM */
+  minAmount?: number;
+  /** Maximum allowed contribution in XLM */
+  maxAmount?: number;
+  /** Pre-filled amount (e.g. from group settings) */
+  defaultAmount?: number;
+  /** Current cycle number */
+  cycleId: number;
+  /** Connected wallet address */
+  walletAddress?: string;
+  /** Called with tx hash on success */
+  onSuccess?: (txHash: string, amount: number) => void;
+  /** Called on terminal error */
+  onError?: (error: Error) => void;
+  /** Disable the trigger button */
+  disabled?: boolean;
+}
+ 
+// ── Validation ───────────────────────────────────────────────────────────────
+ 
+function validateAmount(raw: string, min: number, max: number): string | null {
+  const value = parseFloat(raw);
+  if (!raw.trim() || isNaN(value)) return 'Please enter a valid amount.';
+  if (value <= 0) return 'Amount must be greater than 0.';
+  if (value < min) return `Minimum contribution is ${min} XLM.`;
+  if (value > max) return `Maximum contribution is ${max} XLM.`;
+  return null;
+}
+ 
+// ── Mock wallet transaction ──────────────────────────────────────────────────
+ 
+async function signAndSubmit(amount: number): Promise<string> {
+  // Step 1: wallet signing (simulated)
+  await new Promise((r) => setTimeout(r, 1200));
+  if (Math.random() < 0.08) throw new Error('User rejected the transaction in wallet.');
+  // Step 2: network submission
+  await new Promise((r) => setTimeout(r, 900));
+  if (Math.random() < 0.05) throw new Error('Network error: transaction timed out.');
+  return `tx_${amount}_${Math.random().toString(36).slice(2, 14)}`;
+}
+ 
+// ── Status display config ────────────────────────────────────────────────────
+ 
+const STATUS_LABEL: Record<TransactionStatus, string> = {
+  idle: '',
+  confirming: 'Waiting for your confirmation...',
+  pending: 'Signing transaction in wallet...',
+  submitting: 'Submitting to Stellar network...',
+  success: 'Contribution confirmed!',
+  error: 'Transaction failed.',
+};
+ 
+const STATUS_SEVERITY: Partial<Record<TransactionStatus, 'info' | 'success' | 'error' | 'warning'>> = {
+  confirming: 'info',
+  pending: 'info',
+  submitting: 'info',
+  success: 'success',
+  error: 'error',
+};
+ 
+// ── Confirmation Dialog ──────────────────────────────────────────────────────
+ 
+interface ConfirmDialogProps {
+  open: boolean;
+  amount: number;
+  cycleId: number;
+  onConfirm: () => void;
+  onCancel: () => void;
+}
+ 
+function ConfirmDialog({ open, amount, cycleId, onConfirm, onCancel }: ConfirmDialogProps) {
+  return (
+    <Dialog open={open} onClose={onCancel} maxWidth="xs" fullWidth>
+      <DialogTitle>Confirm Contribution</DialogTitle>
+      <DialogContent>
+        <Stack spacing={2} sx={{ pt: 1 }}>
+          <Typography variant="body2" color="text.secondary">
+            Cycle #{cycleId}
+          </Typography>
+          <Box sx={{ bgcolor: 'action.hover', borderRadius: 2, p: 2 }}>
+            <Stack spacing={1}>
+              <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
+                <Typography variant="body2" color="text.secondary">Amount</Typography>
+                <Typography variant="body2" fontWeight={700}>{amount} XLM</Typography>
+              </Box>
+              <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
+                <Typography variant="body2" color="text.secondary">Network fee</Typography>
+                <Typography variant="body2" color="text.secondary">~0.00001 XLM</Typography>
+              </Box>
+              <Divider />
+              <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
+                <Typography variant="body2" fontWeight={600}>Total</Typography>
+                <Typography variant="body2" fontWeight={700} color="primary">{amount} XLM</Typography>
+              </Box>
+            </Stack>
+          </Box>
+          <Typography variant="caption" color="text.secondary" textAlign="center">
+            Your wallet will prompt you to approve this transaction.
+          </Typography>
+        </Stack>
+      </DialogContent>
+      <DialogActions>
+        <Button variant="secondary" onClick={onCancel}>Cancel</Button>
+        <Button variant="primary" onClick={onConfirm}>Confirm &amp; Sign</Button>
+      </DialogActions>
+    </Dialog>
+  );
+}
+ 
+// ── Main Component ───────────────────────────────────────────────────────────
+ 
+/**
+ * ContributionFlow — Issue #442
+ * Full user flow for making contributions:
+ * - Contribution amount form with validation
+ * - Confirmation dialog
+ * - Wallet signing simulation
+ * - Transaction status display
+ * - Success / error messages
+ * - Retry functionality
+ */
+export function ContributionFlow({
+  minAmount = 1,
+  maxAmount = 100000,
+  defaultAmount,
+  cycleId,
+  walletAddress,
+  onSuccess,
+  onError,
+  disabled = false,
+}: ContributionFlowProps) {
+  const [amountInput, setAmountInput] = useState(defaultAmount ? String(defaultAmount) : '');
+  const [fieldError, setFieldError] = useState<string | null>(null);
+  const [confirmOpen, setConfirmOpen] = useState(false);
+  const [status, setStatus] = useState<TransactionStatus>('idle');
+  const [txHash, setTxHash] = useState<string | null>(null);
+  const [errorMessage, setErrorMessage] = useState<string | null>(null);
+  const [showSuccess, setShowSuccess] = useState(false);
+ 
+  const isProcessing = ['confirming', 'pending', 'submitting'].includes(status);
+  const parsedAmount = parseFloat(amountInput);
+ 
+  const handleSubmitForm = (e: React.FormEvent) => {
+    e.preventDefault();
+    const err = validateAmount(amountInput, minAmount, maxAmount);
+    if (err) { setFieldError(err); return; }
+    setFieldError(null);
+    setConfirmOpen(true);
+  };
+ 
+  const handleConfirm = async () => {
+    setConfirmOpen(false);
+    setStatus('confirming');
+    setErrorMessage(null);
+    setTxHash(null);
+    try {
+      setStatus('pending');
+      const hash = await signAndSubmit(parsedAmount);
+      setStatus('submitting');
+      await new Promise((r) => setTimeout(r, 500));
+      setTxHash(hash);
+      setStatus('success');
+      setShowSuccess(true);
+      onSuccess?.(hash, parsedAmount);
+    } catch (err) {
+      const error = err instanceof Error ? err : new Error('Transaction failed');
+      setErrorMessage(error.message);
+      setStatus('error');
+      onError?.(error);
+    }
+  };
+ 
+  const handleRetry = () => {
+    setStatus('idle');
+    setErrorMessage(null);
+    setTxHash(null);
+  };
+ 
+  const handleReset = () => {
+    setStatus('idle');
+    setAmountInput(defaultAmount ? String(defaultAmount) : '');
+    setFieldError(null);
+    setErrorMessage(null);
+    setTxHash(null);
+    setShowSuccess(false);
+  };
+ 
+  const statusSeverity = STATUS_SEVERITY[status];
+ 
+  return (
+    <Box>
+      {/* Wallet not connected warning */}
+      {!walletAddress && (
+        <Alert severity="warning" sx={{ mb: 2 }}>
+          Connect your wallet to make a contribution.
+        </Alert>
+      )}
+ 
+      {/* Transaction status banner */}
+      {statusSeverity && STATUS_LABEL[status] && (
+        <Alert
+          severity={statusSeverity}
+          sx={{ mb: 2 }}
+          action={
+            status === 'error' ? (
+              <Button variant="ghost" size="sm" onClick={handleRetry}>Retry</Button>
+            ) : status === 'success' ? (
+              <Button variant="ghost" size="sm" onClick={handleReset}>New</Button>
+            ) : undefined
+          }
+        >
+          <Stack spacing={0.5}>
+            <span>{STATUS_LABEL[status]}</span>
+            {txHash && status === 'success' && (
+              <a
+                href={`https://stellar.expert/explorer/testnet/tx/${txHash}`}
+                target="_blank"
+                rel="noopener noreferrer"
+                style={{ fontSize: '0.75rem', color: 'inherit' }}
+              >
+                View on Stellar Explorer →
+              </a>
+            )}
+          </Stack>
+        </Alert>
+      )}
+ 
+      {/* Contribution form — hidden while processing or after success */}
+      {status !== 'success' && (
+        <Box component="form" onSubmit={handleSubmitForm} noValidate>
+          <Stack spacing={2}>
+            <TextField
+              label="Contribution Amount (XLM)"
+              type="number"
+              value={amountInput}
+              onChange={(e) => { setAmountInput(e.target.value); setFieldError(null); }}
+              error={!!fieldError}
+              helperText={fieldError ?? `Min: ${minAmount} XLM · Max: ${maxAmount.toLocaleString()} XLM`}
+              inputProps={{ min: minAmount, max: maxAmount, step: '0.01' }}
+              disabled={isProcessing || !walletAddress || disabled}
+              fullWidth
+              size="small"
+            />
+ 
+            {/* Amount quick-select chips */}
+            {defaultAmount && (
+              <Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
+                {[defaultAmount * 0.5, defaultAmount, defaultAmount * 2].map((v) => (
+                  <Chip
+                    key={v}
+                    label={`${v} XLM`}
+                    size="small"
+                    variant={parseFloat(amountInput) === v ? 'filled' : 'outlined'}
+                    color={parseFloat(amountInput) === v ? 'primary' : 'default'}
+                    onClick={() => { setAmountInput(String(v)); setFieldError(null); }}
+                    disabled={isProcessing || !walletAddress || disabled}
+                    sx={{ cursor: 'pointer' }}
+                  />
+                ))}
+              </Box>
+            )}
+ 
+            <Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
+              <Button
+                type="submit"
+                variant="primary"
+                disabled={isProcessing || !walletAddress || disabled}
+              >
+                {isProcessing ? (
+                  <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
+                    <CircularProgress size={16} color="inherit" />
+                    Processing...
+                  </Box>
+                ) : 'Contribute'}
+              </Button>
+              {status === 'error' && (
+                <Button variant="secondary" onClick={handleRetry}>Try Again</Button>
+              )}
+            </Box>
+          </Stack>
+        </Box>
+      )}
+ 
+      {/* Success state */}
+      {status === 'success' && (
+        <Stack spacing={2} alignItems="center" sx={{ py: 2 }}>
+          <Typography variant="h6" color="success.main">🎉 Contribution Successful!</Typography>
+          <Typography variant="body2" color="text.secondary">
+            {parsedAmount} XLM contributed to Cycle #{cycleId}
+          </Typography>
+          <Button variant="secondary" onClick={handleReset}>Make Another Contribution</Button>
+        </Stack>
+      )}
+ 
+      {/* Confirmation dialog */}
+      <ConfirmDialog
+        open={confirmOpen}
+        amount={parsedAmount || 0}
+        cycleId={cycleId}
+        onConfirm={() => void handleConfirm()}
+        onCancel={() => setConfirmOpen(false)}
+      />
+ 
+      {/* Success animation modal */}
+      <ContributionSuccessModal
+        open={showSuccess}
+        amount={parsedAmount || 0}
+        cycleId={cycleId}
+        txHash={txHash ?? undefined}
+        onClose={handleReset}
+      />
+    </Box>
+  );
+}
+ 
+export default ContributionFlow;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/GroupCard.tsx.html b/frontend/coverage/GroupCard.tsx.html new file mode 100644 index 00000000..bd6d6dd7 --- /dev/null +++ b/frontend/coverage/GroupCard.tsx.html @@ -0,0 +1,937 @@ + + + + + + Code coverage report for GroupCard.tsx + + + + + + + + + +
+
+

All files GroupCard.tsx

+
+ +
+ 100% + Statements + 166/166 +
+ + +
+ 90.9% + Branches + 30/33 +
+ + +
+ 100% + Functions + 9/9 +
+ + +
+ 100% + Lines + 166/166 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +2851x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +7x +7x +7x +7x +7x +7x +  +7x +7x +7x +7x +7x +7x +7x +7x +7x +7x +  +35x +35x +8x +8x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +  +35x +1x +1x +1x +  +35x +35x +35x +8x +8x +8x +  +  +35x +35x +35x +35x +  +35x +8x +8x +8x +  +  +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +35x +  +35x +35x +4x +4x +4x +4x +4x +  +4x +  +35x +5x +5x +5x +5x +5x +  +5x +  +35x +35x +  +  +35x +8x +8x +8x +8x +8x +8x +  +8x +8x +  +8x +  +27x +27x +27x +27x +  +27x +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +49x +  +  +49x +49x +49x +49x +49x +49x +  +49x +21x +  +21x +3x +3x +3x +3x +3x +3x +  +3x +  +7x +7x +  +7x +7x +7x +7x +7x +7x +7x +7x +7x +7x +7x +7x +7x +7x +7x +7x +  +7x +  +  +28x +49x +  +49x +49x +49x +49x +49x +49x +49x +49x +49x +49x +49x +49x +49x +49x +49x +49x +  +49x + 
import { Link } from 'react-router-dom';
+import { useQuery } from '@tanstack/react-query';
+import { buildRoute } from '../routing/constants';
+import { fetchGroup } from '../utils/groupApi';
+import type { GroupDetail } from '../types/group';
+import { GroupBadge } from './GroupBadge';
+import { GroupCardSkeleton } from './Skeleton/GroupCardSkeleton';
+import { Button } from './Button';
+import './GroupCard.css';
+ 
+// ─── Types ────────────────────────────────────────────────────────────────────
+ 
+type Status = 'active' | 'completed' | 'pending' | 'complete';
+ 
+/** Prop-driven mode: caller supplies all data directly. */
+interface GroupCardStaticProps {
+  groupId?: string;
+  groupName: string;
+  memberCount: number;
+  contributionAmount: number;
+  currency?: string;
+  status?: Status;
+  currentCycle?: number;
+  nextPayoutDate?: Date | null;
+  description?: string;
+  imageUrl?: string;
+  onClick?: () => void;
+  onViewDetails?: () => void;
+  onJoin?: () => void;
+  className?: string;
+}
+ 
+/** Fetch mode: only groupId is required; data is loaded via React Query. */
+interface GroupCardFetchProps {
+  groupId: string;
+  groupName?: never;
+  memberCount?: never;
+  contributionAmount?: never;
+  currency?: string;
+  status?: never;
+  currentCycle?: never;
+  nextPayoutDate?: never;
+  description?: never;
+  imageUrl?: never;
+  onClick?: () => void;
+  onViewDetails?: () => void;
+  onJoin?: () => void;
+  className?: string;
+}
+ 
+export type GroupCardProps = GroupCardStaticProps | GroupCardFetchProps;
+ 
+// ─── Helpers ──────────────────────────────────────────────────────────────────
+ 
+const STROOPS_PER_XLM = 10_000_000;
+ 
+function formatXlm(stroops: number): string {
+  return (stroops / STROOPS_PER_XLM).toLocaleString('en-US', {
+    minimumFractionDigits: 0,
+    maximumFractionDigits: 2,
+  });
+}
+ 
+function computeNextPayout(
+  startedAt: Date | null,
+  currentCycle: number,
+  cycleDurationSecs: number,
+): Date | null {
+  if (!startedAt || cycleDurationSecs <= 0) return null;
+  const nextCycleEnd =
+    startedAt.getTime() + (currentCycle + 1) * cycleDurationSecs * 1000;
+  return new Date(nextCycleEnd);
+}
+ 
+function formatDate(date: Date | null | undefined): string {
+  if (!date) return '—';
+  return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
+}
+ 
+// ─── Inner UI ─────────────────────────────────────────────────────────────────
+ 
+interface CardUIProps {
+  groupId?: string;
+  groupName: string;
+  memberCount: number;
+  contributionAmount: string;
+  status: Status;
+  currentCycle: number;
+  nextPayoutDate: Date | null | undefined;
+  description?: string;
+  imageUrl?: string;
+  onClick?: () => void;
+  onViewDetails?: () => void;
+  onJoin?: () => void;
+  className?: string;
+}
+ 
+function GroupCardUI({
+  groupId,
+  groupName,
+  memberCount,
+  contributionAmount,
+  status,
+  currentCycle,
+  nextPayoutDate,
+  description,
+  imageUrl,
+  onClick,
+  onViewDetails,
+  onJoin,
+  className = '',
+}: CardUIProps) {
+  const classes = ['group-card', className].filter(Boolean).join(' ');
+ 
+  const handleCardClick = (e: React.MouseEvent) => {
+    if ((e.target as HTMLElement).closest('button')) return;
+    onClick?.();
+  };
+ 
+  const content = (
+    <>
+      {imageUrl && (
+        <div className="group-card-image">
+          <img src={imageUrl} alt={groupName} />
+        </div>
+      )}
+ 
+      <div className="group-card-header">
+        <h3 className="group-card-title">{groupName}</h3>
+        <GroupBadge status={status} />
+      </div>
+ 
+      {description && (
+        <div className="group-card-description">
+          <p>{description}</p>
+        </div>
+      )}
+ 
+      <div className="group-card-body">
+        <div className="group-card-stats">
+          <div className="group-card-stat">
+            <span className="group-card-stat-label">Contribution</span>
+            <span className="group-card-stat-value">{contributionAmount}</span>
+          </div>
+          <div className="group-card-stat">
+            <span className="group-card-stat-label">Members</span>
+            <span className="group-card-stat-value">{memberCount}</span>
+          </div>
+          <div className="group-card-stat">
+            <span className="group-card-stat-label">Cycle</span>
+            <span className="group-card-stat-value">{currentCycle}</span>
+          </div>
+          <div className="group-card-stat">
+            <span className="group-card-stat-label">Next Payout</span>
+            <span className="group-card-stat-value group-card-stat-value--date">
+              {formatDate(nextPayoutDate)}
+            </span>
+          </div>
+        </div>
+      </div>
+ 
+      <div className="group-card-footer">
+        {onViewDetails && (
+          <Button
+            variant="secondary"
+            size="sm"
+            onClick={(e) => { e.stopPropagation(); onViewDetails(); }}
+          >
+            View Details
+          </Button>
+        )}
+        {onJoin && (
+          <Button
+            variant="primary"
+            size="sm"
+            onClick={(e) => { e.stopPropagation(); onJoin(); }}
+          >
+            Join Group
+          </Button>
+        )}
+      </div>
+    </>
+  );
+ 
+  if (groupId) {
+    return (
+      <Link
+        to={buildRoute.groupDetail(groupId)}
+        className={classes}
+        style={{ textDecoration: 'none', color: 'inherit' }}
+        onClick={handleCardClick}
+      >
+        {content}
+      </Link>
+    );
+  }
+ 
+  return (
+    <div className={classes} onClick={handleCardClick}>
+      {content}
+    </div>
+  );
+}
+ 
+// ─── Public component ─────────────────────────────────────────────────────────
+ 
+/**
+ * GroupCard — displays group name, contribution amount, current cycle,
+ * member count, next payout date, and a status badge.
+ *
+ * Two modes:
+ * - **Static**: pass all data as props (backward-compatible with existing usage).
+ * - **Fetch**: pass only `groupId`; data is fetched via React Query using the
+ *   Soroban RPC client (`fetchGroup`). Shows a skeleton while loading and an
+ *   inline error on failure.
+ */
+export function GroupCard(props: GroupCardProps) {
+  const isFetchMode = props.groupId !== undefined && props.groupName === undefined;
+ 
+  // Fetch mode — React Query
+  const { data, isLoading, error } = useQuery({
+    queryKey: ['group', props.groupId],
+    queryFn: () => fetchGroup(props.groupId!) as Promise<GroupDetail | null>,
+    enabled: isFetchMode,
+    staleTime: 30_000,
+  });
+ 
+  if (isFetchMode) {
+    if (isLoading) return <GroupCardSkeleton />;
+ 
+    if (error || !data) {
+      return (
+        <div className="group-card group-card--error" role="alert">
+          <p className="group-card-error-msg">
+            {error instanceof Error ? error.message : 'Failed to load group.'}
+          </p>
+        </div>
+      );
+    }
+ 
+    const nextPayout = computeNextPayout(data.startedAt, data.currentCycle, data.cycleDuration);
+    const amountStr = `${formatXlm(data.contributionAmount)} ${data.currency}`;
+ 
+    return (
+      <GroupCardUI
+        groupId={data.id}
+        groupName={data.name}
+        memberCount={data.memberCount}
+        contributionAmount={amountStr}
+        status={data.status as Status}
+        currentCycle={data.currentCycle}
+        nextPayoutDate={nextPayout}
+        description={data.description}
+        imageUrl={data.imageUrl}
+        onClick={props.onClick}
+        onViewDetails={props.onViewDetails}
+        onJoin={props.onJoin}
+        className={props.className}
+      />
+    );
+  }
+ 
+  // Static mode — props supplied directly (backward-compatible)
+  const p = props as GroupCardStaticProps;
+  const amountStr = `${(p.contributionAmount ?? 0).toLocaleString()} ${p.currency ?? 'XLM'}`;
+ 
+  return (
+    <GroupCardUI
+      groupId={p.groupId}
+      groupName={p.groupName}
+      memberCount={p.memberCount}
+      contributionAmount={amountStr}
+      status={p.status ?? 'active'}
+      currentCycle={p.currentCycle ?? 0}
+      nextPayoutDate={p.nextPayoutDate}
+      description={p.description}
+      imageUrl={p.imageUrl}
+      onClick={p.onClick}
+      onViewDetails={p.onViewDetails}
+      onJoin={p.onJoin}
+      className={p.className}
+    />
+  );
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/TransactionHistory.tsx.html b/frontend/coverage/TransactionHistory.tsx.html new file mode 100644 index 00000000..9720abba --- /dev/null +++ b/frontend/coverage/TransactionHistory.tsx.html @@ -0,0 +1,1132 @@ + + + + + + Code coverage report for TransactionHistory.tsx + + + + + + + + + +
+
+

All files TransactionHistory.tsx

+
+ +
+ 99.58% + Statements + 242/243 +
+ + +
+ 73.58% + Branches + 39/53 +
+ + +
+ 80% + Functions + 12/15 +
+ + +
+ 99.58% + Lines + 242/243 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +3501x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +1x +1x +1x +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +10x +10x +10x +10x +10x +10x +10x +  +10x +10x +  +6x +10x +  +10x +10x +10x +10x +10x +10x +10x +10x +10x +10x +10x +10x +10x +10x +10x +10x +  +  +  +1x +1x +1x +1x +1x +1x +1x +  +  +  +1x +1x +1x +1x +1x +1x +1x +1x +  +16x +16x +1x +15x +  +16x +16x +16x +16x +16x +16x +47x +16x +16x +16x +16x +16x +16x +16x +16x +47x +47x +47x +47x +47x +47x +  +16x +16x +16x +16x +16x +16x +16x +16x +47x +47x +47x +47x +47x +47x +  +47x +47x +  +47x +16x +16x +16x +16x +16x +16x +16x +16x +16x +16x +16x +16x +16x +47x +47x +47x +  +16x +16x +16x +16x +16x +16x +16x +16x +47x +47x +47x +  +16x +16x +16x +16x +16x +16x +16x +47x +47x +47x +47x +47x +  +16x +16x +16x +16x +16x +16x +16x +47x +47x +47x +47x +47x +47x +47x +  +47x +  +16x +16x +16x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +41x +41x +41x +41x +41x +41x +41x +  +41x +41x +41x +41x +41x +41x +  +  +41x +16x +6x +6x +6x +10x +10x +10x +10x +6x +10x +10x +  +4x +4x +10x +10x +41x +  +  +41x +31x +  +41x +  +41x +  +41x +41x +41x +41x +  +41x +41x +  +41x +41x +  +41x +41x +41x +41x +41x +41x +41x +41x +  +41x +41x +45x +41x +41x +41x +12x +  +12x +  +41x +  +41x +  +  +41x +41x +41x +41x +41x +41x +41x +41x +41x +41x +41x +41x +41x +41x +19x +19x +19x +  +41x +32x +32x +32x +  +41x +41x +41x +41x +41x +41x +41x +41x +41x +41x +41x +  +41x +  +1x + 
/**
+ * TransactionHistory — Issue #769
+ *
+ * Paginated table component displaying a member's full contribution and
+ * payout history. Uses MUI DataGrid with:
+ * - Sorting by date, amount, and type
+ * - Horizon API fetch filtered by account and contract ID
+ * - Loading and empty states
+ */
+import { useMemo, useState, useEffect } from 'react';
+import {
+  Box,
+  Chip,
+  Typography,
+  TextField,
+  MenuItem,
+  Stack,
+  CircularProgress,
+  Alert,
+  Link,
+} from '@mui/material';
+import {
+  DataGrid,
+  type GridColDef,
+  type GridSortModel,
+  type GridRenderCellParams,
+} from '@mui/x-data-grid';
+import type { Transaction, TransactionType } from '../types/transaction';
+import { useWallet } from '../hooks/useWallet';
+ 
+// ── Horizon fetch ─────────────────────────────────────────────────────────────
+ 
+const HORIZON_URLS: Record<string, string> = {
+  TESTNET: 'https://horizon-testnet.stellar.org',
+  MAINNET: 'https://horizon.stellar.org',
+  FUTURENET: 'https://horizon-futurenet.stellar.org',
+};
+ 
+interface HorizonPayment {
+  id: string;
+  type: string;
+  created_at: string;
+  transaction_hash: string;
+  amount?: string;
+  asset_code?: string;
+  asset_type?: string;
+  from?: string;
+  to?: string;
+  memo?: string;
+}
+ 
+async function fetchHorizonTransactions(
+  address: string,
+  network: string,
+  contractId?: string,
+): Promise<Transaction[]> {
+  const base = HORIZON_URLS[network] ?? HORIZON_URLS.TESTNET;
+  const url = `${base}/accounts/${address}/payments?limit=50&order=desc`;
+ 
+  const res = await fetch(url);
+  if (!res.ok) throw new Error(`Horizon error: ${res.status}`);
+ 
+  const data = (await res.json()) as { _embedded: { records: HorizonPayment[] } };
+  const records = data._embedded?.records ?? [];
+ 
+  return records
+    .filter((r) => !contractId || r.transaction_hash.includes(contractId.slice(0, 8)))
+    .map((r): Transaction => ({
+      id: r.id,
+      hash: r.transaction_hash,
+      createdAt: r.created_at,
+      type: (r.type === 'payment' ? 'payment' : 'deposit') as TransactionType,
+      amount: r.amount ? `+${r.amount}` : '0',
+      assetCode: r.asset_code ?? (r.asset_type === 'native' ? 'XLM' : 'UNKNOWN'),
+      from: r.from ?? address,
+      to: r.to,
+      memo: r.memo,
+      status: 'success',
+      fee: '0.00001',
+    }));
+}
+ 
+// ── Mock fallback ─────────────────────────────────────────────────────────────
+ 
+const MOCK_TRANSACTIONS: Transaction[] = [
+  { id: '1', hash: 'abc123def456abc123def456abc123de', createdAt: '2026-04-20T10:30:00Z', type: 'deposit', amount: '+250', assetCode: 'XLM', from: 'GABC...', to: 'GDEF...', memo: 'Group contribution cycle 2', status: 'success', fee: '0.00001' },
+  { id: '2', hash: 'def456ghi789def456ghi789def456gh', createdAt: '2026-04-15T14:22:00Z', type: 'payment', amount: '+1000', assetCode: 'XLM', from: 'GDEF...', to: 'GABC...', memo: 'Payout cycle 1', status: 'success', fee: '0.00001' },
+  { id: '3', hash: 'ghi789jkl012ghi789jkl012ghi789jk', createdAt: '2026-03-20T09:15:00Z', type: 'deposit', amount: '+250', assetCode: 'XLM', from: 'GABC...', to: 'GDEF...', memo: 'Group contribution cycle 1', status: 'success', fee: '0.00001' },
+  { id: '4', hash: 'jkl012mno345jkl012mno345jkl012mn', createdAt: '2026-03-10T16:45:00Z', type: 'withdraw', amount: '-45.50', assetCode: 'USDC', from: 'GABC...', to: 'GXYZ...', memo: '', status: 'success', fee: '0.00001' },
+  { id: '5', hash: 'mno345pqr678mno345pqr678mno345pq', createdAt: '2026-02-28T11:20:00Z', type: 'claimable', amount: '+15.75', assetCode: 'USDC', from: 'GXYZ...', memo: 'Reward claim', status: 'pending', fee: '0.00001' },
+];
+ 
+// ── Column definitions ────────────────────────────────────────────────────────
+ 
+const TYPE_COLORS: Record<string, 'success' | 'primary' | 'warning' | 'error' | 'default'> = {
+  deposit: 'success',
+  payment: 'primary',
+  withdraw: 'warning',
+  swap: 'default',
+  claimable: 'success',
+  other: 'default',
+};
+ 
+function buildColumns(network: string): GridColDef[] {
+  const explorerBase = network === 'MAINNET'
+    ? 'https://stellar.expert/explorer/public/tx'
+    : 'https://stellar.expert/explorer/testnet/tx';
+ 
+  return [
+    {
+      field: 'createdAt',
+      headerName: 'Date',
+      width: 160,
+      valueFormatter: (value: string) =>
+        new Date(value).toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' }),
+      sortable: true,
+    },
+    {
+      field: 'type',
+      headerName: 'Type',
+      width: 130,
+      sortable: true,
+      renderCell: (params: GridRenderCellParams<Transaction, string>) => (
+        <Chip
+          label={params.value ?? ''}
+          size="small"
+          color={TYPE_COLORS[params.value ?? ''] ?? 'default'}
+          sx={{ textTransform: 'capitalize', fontWeight: 600 }}
+        />
+      ),
+    },
+    {
+      field: 'amount',
+      headerName: 'Amount',
+      width: 140,
+      sortable: true,
+      sortComparator: (a: string, b: string) => parseFloat(a) - parseFloat(b),
+      renderCell: (params: GridRenderCellParams<Transaction, string>) => {
+        const val = parseFloat(params.value ?? '0');
+        return (
+          <Typography
+            variant="body2"
+            fontWeight={600}
+            color={val >= 0 ? 'success.main' : 'error.main'}
+          >
+            {params.value} {params.row.assetCode}
+          </Typography>
+        );
+      },
+    },
+    {
+      field: 'assetCode',
+      headerName: 'Asset',
+      width: 90,
+      sortable: true,
+    },
+    {
+      field: 'from',
+      headerName: 'From',
+      width: 140,
+      sortable: false,
+      renderCell: (params: GridRenderCellParams<Transaction, string>) => (
+        <Typography variant="caption" sx={{ fontFamily: 'monospace' }}>
+          {params.value}
+        </Typography>
+      ),
+    },
+    {
+      field: 'memo',
+      headerName: 'Memo',
+      flex: 1,
+      minWidth: 160,
+      sortable: false,
+      renderCell: (params: GridRenderCellParams<Transaction, string>) => (
+        <Typography variant="caption" color="text.secondary" noWrap>
+          {params.value || '—'}
+        </Typography>
+      ),
+    },
+    {
+      field: 'status',
+      headerName: 'Status',
+      width: 110,
+      sortable: true,
+      renderCell: (params: GridRenderCellParams<Transaction, string>) => (
+        <Chip
+          label={params.value}
+          size="small"
+          color={params.value === 'success' ? 'success' : params.value === 'pending' ? 'warning' : 'error'}
+        />
+      ),
+    },
+    {
+      field: 'hash',
+      headerName: 'TX',
+      width: 80,
+      sortable: false,
+      renderCell: (params: GridRenderCellParams<Transaction, string>) => (
+        <Link
+          href={`${explorerBase}/${params.value}`}
+          target="_blank"
+          rel="noopener noreferrer"
+          variant="caption"
+          onClick={(e) => e.stopPropagation()}
+        >
+          View ↗
+        </Link>
+      ),
+    },
+  ];
+}
+ 
+// ── Props ─────────────────────────────────────────────────────────────────────
+ 
+interface TransactionHistoryProps {
+  /** Override the wallet address to query (defaults to connected wallet) */
+  address?: string;
+  /** Optional Soroban contract ID to filter transactions */
+  contractId?: string;
+  /** Initial page size */
+  pageSize?: number;
+}
+ 
+// ── Component ─────────────────────────────────────────────────────────────────
+ 
+/**
+ * TransactionHistory
+ *
+ * Fetches and displays a paginated, sortable table of Stellar transactions
+ * for the connected wallet address, filtered by account and optionally by
+ * contract ID.
+ */
+export function TransactionHistory({
+  address: addressProp,
+  contractId,
+  pageSize: initialPageSize = 10,
+}: TransactionHistoryProps) {
+  const { activeAddress, network } = useWallet();
+  const address = addressProp ?? activeAddress;
+  const net = network ?? 'TESTNET';
+ 
+  const [transactions, setTransactions] = useState<Transaction[]>([]);
+  const [loading, setLoading] = useState(false);
+  const [error, setError] = useState<string | null>(null);
+  const [typeFilter, setTypeFilter] = useState<string>('all');
+  const [sortModel, setSortModel] = useState<GridSortModel>([{ field: 'createdAt', sort: 'desc' }]);
+  const [paginationModel, setPaginationModel] = useState({ page: 0, pageSize: initialPageSize });
+ 
+  // Fetch from Horizon (falls back to mock if no address or fetch fails)
+  useEffect(() => {
+    if (!address) {
+      setTransactions(MOCK_TRANSACTIONS);
+      return;
+    }
+    setLoading(true);
+    setError(null);
+    fetchHorizonTransactions(address, net, contractId)
+      .then((txs) => {
+        setTransactions(txs.length > 0 ? txs : MOCK_TRANSACTIONS);
+      })
+      .catch(() => {
+        // Graceful fallback to mock data
+        setTransactions(MOCK_TRANSACTIONS);
+        setError(null); // don't show error — just use mock
+      })
+      .finally(() => setLoading(false));
+  }, [address, net, contractId]);
+ 
+  // Client-side type filter
+  const filtered = useMemo(() => {
+    if (typeFilter === 'all') return transactions;
+    return transactions.filter((tx) => tx.type === typeFilter);
+  }, [transactions, typeFilter]);
+ 
+  const columns = useMemo(() => buildColumns(net), [net]);
+ 
+  const uniqueTypes = useMemo(
+    () => Array.from(new Set(transactions.map((t) => t.type))),
+    [transactions],
+  );
+ 
+  return (
+    <Box>
+      {/* Toolbar */}
+      <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} sx={{ mb: 2 }} alignItems={{ sm: 'center' }}>
+        <Typography variant="h3" sx={{ flex: 1 }}>
+          Transaction History
+        </Typography>
+        <TextField
+          select
+          size="small"
+          label="Type"
+          value={typeFilter}
+          onChange={(e) => setTypeFilter(e.target.value)}
+          sx={{ minWidth: 140 }}
+        >
+          <MenuItem value="all">All types</MenuItem>
+          {uniqueTypes.map((t) => (
+            <MenuItem key={t} value={t} sx={{ textTransform: 'capitalize' }}>{t}</MenuItem>
+          ))}
+        </TextField>
+        {!address && (
+          <Typography variant="caption" color="text.secondary">
+            Showing demo data — connect wallet to see real transactions
+          </Typography>
+        )}
+      </Stack>
+ 
+      {error && <Alert severity="warning" sx={{ mb: 2 }}>{error}</Alert>}
+ 
+      {/* DataGrid */}
+      <Box sx={{ height: 480, width: '100%' }}>
+        <DataGrid
+          rows={filtered}
+          columns={columns}
+          loading={loading}
+          sortModel={sortModel}
+          onSortModelChange={setSortModel}
+          paginationModel={paginationModel}
+          onPaginationModelChange={setPaginationModel}
+          pageSizeOptions={[5, 10, 25, 50]}
+          disableRowSelectionOnClick
+          density="compact"
+          slots={{
+            loadingOverlay: () => (
+              <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
+                <CircularProgress size={32} />
+              </Box>
+            ),
+            noRowsOverlay: () => (
+              <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
+                <Typography color="text.secondary">No transactions found</Typography>
+              </Box>
+            ),
+          }}
+          sx={{
+            border: '1px solid',
+            borderColor: 'divider',
+            borderRadius: 2,
+            '& .MuiDataGrid-columnHeaders': { bgcolor: 'action.hover' },
+            '& .MuiDataGrid-row:hover': { bgcolor: 'action.hover' },
+          }}
+        />
+      </Box>
+    </Box>
+  );
+}
+ 
+export default TransactionHistory;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/base.css b/frontend/coverage/base.css new file mode 100644 index 00000000..f418035b --- /dev/null +++ b/frontend/coverage/base.css @@ -0,0 +1,224 @@ +body, html { + margin:0; padding: 0; + height: 100%; +} +body { + font-family: Helvetica Neue, Helvetica, Arial; + font-size: 14px; + color:#333; +} +.small { font-size: 12px; } +*, *:after, *:before { + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + } +h1 { font-size: 20px; margin: 0;} +h2 { font-size: 14px; } +pre { + font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; + margin: 0; + padding: 0; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +a { color:#0074D9; text-decoration:none; } +a:hover { text-decoration:underline; } +.strong { font-weight: bold; } +.space-top1 { padding: 10px 0 0 0; } +.pad2y { padding: 20px 0; } +.pad1y { padding: 10px 0; } +.pad2x { padding: 0 20px; } +.pad2 { padding: 20px; } +.pad1 { padding: 10px; } +.space-left2 { padding-left:55px; } +.space-right2 { padding-right:20px; } +.center { text-align:center; } +.clearfix { display:block; } +.clearfix:after { + content:''; + display:block; + height:0; + clear:both; + visibility:hidden; + } +.fl { float: left; } +@media only screen and (max-width:640px) { + .col3 { width:100%; max-width:100%; } + .hide-mobile { display:none!important; } +} + +.quiet { + color: #7f7f7f; + color: rgba(0,0,0,0.5); +} +.quiet a { opacity: 0.7; } + +.fraction { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 10px; + color: #555; + background: #E8E8E8; + padding: 4px 5px; + border-radius: 3px; + vertical-align: middle; +} + +div.path a:link, div.path a:visited { color: #333; } +table.coverage { + border-collapse: collapse; + margin: 10px 0 0 0; + padding: 0; +} + +table.coverage td { + margin: 0; + padding: 0; + vertical-align: top; +} +table.coverage td.line-count { + text-align: right; + padding: 0 5px 0 20px; +} +table.coverage td.line-coverage { + text-align: right; + padding-right: 10px; + min-width:20px; +} + +table.coverage td span.cline-any { + display: inline-block; + padding: 0 5px; + width: 100%; +} +.missing-if-branch { + display: inline-block; + margin-right: 5px; + border-radius: 3px; + position: relative; + padding: 0 4px; + background: #333; + color: yellow; +} + +.skip-if-branch { + display: none; + margin-right: 10px; + position: relative; + padding: 0 4px; + background: #ccc; + color: white; +} +.missing-if-branch .typ, .skip-if-branch .typ { + color: inherit !important; +} +.coverage-summary { + border-collapse: collapse; + width: 100%; +} +.coverage-summary tr { border-bottom: 1px solid #bbb; } +.keyline-all { border: 1px solid #ddd; } +.coverage-summary td, .coverage-summary th { padding: 10px; } +.coverage-summary tbody { border: 1px solid #bbb; } +.coverage-summary td { border-right: 1px solid #bbb; } +.coverage-summary td:last-child { border-right: none; } +.coverage-summary th { + text-align: left; + font-weight: normal; + white-space: nowrap; +} +.coverage-summary th.file { border-right: none !important; } +.coverage-summary th.pct { } +.coverage-summary th.pic, +.coverage-summary th.abs, +.coverage-summary td.pct, +.coverage-summary td.abs { text-align: right; } +.coverage-summary td.file { white-space: nowrap; } +.coverage-summary td.pic { min-width: 120px !important; } +.coverage-summary tfoot td { } + +.coverage-summary .sorter { + height: 10px; + width: 7px; + display: inline-block; + margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; +} +.coverage-summary .sorted .sorter { + background-position: 0 -20px; +} +.coverage-summary .sorted-desc .sorter { + background-position: 0 -10px; +} +.status-line { height: 10px; } +/* yellow */ +.cbranch-no { background: yellow !important; color: #111; } +/* dark red */ +.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } +.low .chart { border:1px solid #C21F39 } +.highlighted, +.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ + background: #C21F39 !important; +} +/* medium red */ +.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +/* light red */ +.low, .cline-no { background:#FCE1E5 } +/* light green */ +.high, .cline-yes { background:rgb(230,245,208) } +/* medium green */ +.cstat-yes { background:rgb(161,215,106) } +/* dark green */ +.status-line.high, .high .cover-fill { background:rgb(77,146,33) } +.high .chart { border:1px solid rgb(77,146,33) } +/* dark yellow (gold) */ +.status-line.medium, .medium .cover-fill { background: #f9cd0b; } +.medium .chart { border:1px solid #f9cd0b; } +/* light yellow */ +.medium { background: #fff4c2; } + +.cstat-skip { background: #ddd; color: #111; } +.fstat-skip { background: #ddd; color: #111 !important; } +.cbranch-skip { background: #ddd !important; color: #111; } + +span.cline-neutral { background: #eaeaea; } + +.coverage-summary td.empty { + opacity: .5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; +} + +.cover-fill, .cover-empty { + display:inline-block; + height: 12px; +} +.chart { + line-height: 0; +} +.cover-empty { + background: white; +} +.cover-full { + border-right: none !important; +} +pre.prettyprint { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { color: #999 !important; } +.ignore-none { color: #999; font-weight: normal; } + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -48px; +} +.footer, .push { + height: 48px; +} diff --git a/frontend/coverage/block-navigation.js b/frontend/coverage/block-navigation.js new file mode 100644 index 00000000..530d1ed2 --- /dev/null +++ b/frontend/coverage/block-navigation.js @@ -0,0 +1,87 @@ +/* eslint-disable */ +var jumpToCode = (function init() { + // Classes of code we would like to highlight in the file view + var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; + + // Elements to highlight in the file listing view + var fileListingElements = ['td.pct.low']; + + // We don't want to select elements that are direct descendants of another match + var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` + + // Selector that finds elements on the page to which we can jump + var selector = + fileListingElements.join(', ') + + ', ' + + notSelector + + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` + + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); + + var currentIndex; + + function toggleClass(index) { + missingCoverageElements + .item(currentIndex) + .classList.remove('highlighted'); + missingCoverageElements.item(index).classList.add('highlighted'); + } + + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + } + + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== 'number' || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; + } + + makeCurrent(nextIndex); + } + + function goToNext() { + var nextIndex = 0; + + if ( + typeof currentIndex === 'number' && + currentIndex < missingCoverageElements.length - 1 + ) { + nextIndex = currentIndex + 1; + } + + makeCurrent(nextIndex); + } + + return function jump(event) { + if ( + document.getElementById('fileSearch') === document.activeElement && + document.activeElement != null + ) { + // if we're currently focused on the search input, we don't want to navigate + return; + } + + switch (event.which) { + case 78: // n + case 74: // j + goToNext(); + break; + case 66: // b + case 75: // k + case 80: // p + goToPrevious(); + break; + } + }; +})(); +window.addEventListener('keydown', jumpToCode); diff --git a/frontend/coverage/clover.xml b/frontend/coverage/clover.xml new file mode 100644 index 00000000..31d376b9 --- /dev/null +++ b/frontend/coverage/clover.xml @@ -0,0 +1,650 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/coverage/coverage-final.json b/frontend/coverage/coverage-final.json new file mode 100644 index 00000000..4e014681 --- /dev/null +++ b/frontend/coverage/coverage-final.json @@ -0,0 +1,4 @@ +{"/home/kimani/drips/Stellar-Save/frontend/src/components/ContributionFlow.tsx": {"path":"/home/kimani/drips/Stellar-Save/frontend/src/components/ContributionFlow.tsx","all":false,"statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":1,"column":33}},"42":{"start":{"line":43,"column":0},"end":{"line":43,"column":79}},"43":{"start":{"line":44,"column":0},"end":{"line":44,"column":32}},"44":{"start":{"line":45,"column":0},"end":{"line":45,"column":73}},"45":{"start":{"line":46,"column":0},"end":{"line":46,"column":58}},"46":{"start":{"line":47,"column":0},"end":{"line":47,"column":64}},"47":{"start":{"line":48,"column":0},"end":{"line":48,"column":64}},"48":{"start":{"line":49,"column":0},"end":{"line":49,"column":14}},"49":{"start":{"line":50,"column":0},"end":{"line":50,"column":1}},"53":{"start":{"line":54,"column":0},"end":{"line":54,"column":63}},"55":{"start":{"line":56,"column":0},"end":{"line":56,"column":48}},"56":{"start":{"line":57,"column":0},"end":{"line":57,"column":88}},"58":{"start":{"line":59,"column":0},"end":{"line":59,"column":47}},"59":{"start":{"line":60,"column":0},"end":{"line":60,"column":85}},"60":{"start":{"line":61,"column":0},"end":{"line":61,"column":67}},"61":{"start":{"line":62,"column":0},"end":{"line":62,"column":1}},"65":{"start":{"line":66,"column":0},"end":{"line":66,"column":57}},"66":{"start":{"line":67,"column":0},"end":{"line":67,"column":11}},"67":{"start":{"line":68,"column":0},"end":{"line":68,"column":49}},"68":{"start":{"line":69,"column":0},"end":{"line":69,"column":46}},"69":{"start":{"line":70,"column":0},"end":{"line":70,"column":49}},"70":{"start":{"line":71,"column":0},"end":{"line":71,"column":37}},"71":{"start":{"line":72,"column":0},"end":{"line":72,"column":31}},"72":{"start":{"line":73,"column":0},"end":{"line":73,"column":2}},"74":{"start":{"line":75,"column":0},"end":{"line":75,"column":103}},"75":{"start":{"line":76,"column":0},"end":{"line":76,"column":21}},"76":{"start":{"line":77,"column":0},"end":{"line":77,"column":18}},"77":{"start":{"line":78,"column":0},"end":{"line":78,"column":21}},"78":{"start":{"line":79,"column":0},"end":{"line":79,"column":21}},"79":{"start":{"line":80,"column":0},"end":{"line":80,"column":17}},"80":{"start":{"line":81,"column":0},"end":{"line":81,"column":2}},"92":{"start":{"line":93,"column":0},"end":{"line":93,"column":92}},"93":{"start":{"line":94,"column":0},"end":{"line":94,"column":10}},"94":{"start":{"line":95,"column":0},"end":{"line":95,"column":67}},"95":{"start":{"line":96,"column":0},"end":{"line":96,"column":53}},"96":{"start":{"line":97,"column":0},"end":{"line":97,"column":21}},"97":{"start":{"line":98,"column":0},"end":{"line":98,"column":42}},"98":{"start":{"line":99,"column":0},"end":{"line":99,"column":61}},"99":{"start":{"line":100,"column":0},"end":{"line":100,"column":28}},"100":{"start":{"line":101,"column":0},"end":{"line":101,"column":23}},"101":{"start":{"line":102,"column":0},"end":{"line":102,"column":71}},"102":{"start":{"line":103,"column":0},"end":{"line":103,"column":31}},"103":{"start":{"line":104,"column":0},"end":{"line":104,"column":77}},"104":{"start":{"line":105,"column":0},"end":{"line":105,"column":86}},"105":{"start":{"line":106,"column":0},"end":{"line":106,"column":86}},"106":{"start":{"line":107,"column":0},"end":{"line":107,"column":20}},"107":{"start":{"line":108,"column":0},"end":{"line":108,"column":77}},"108":{"start":{"line":109,"column":0},"end":{"line":109,"column":91}},"109":{"start":{"line":110,"column":0},"end":{"line":110,"column":92}},"110":{"start":{"line":111,"column":0},"end":{"line":111,"column":20}},"111":{"start":{"line":112,"column":0},"end":{"line":112,"column":25}},"112":{"start":{"line":113,"column":0},"end":{"line":113,"column":77}},"113":{"start":{"line":114,"column":0},"end":{"line":114,"column":79}},"114":{"start":{"line":115,"column":0},"end":{"line":115,"column":102}},"115":{"start":{"line":116,"column":0},"end":{"line":116,"column":20}},"116":{"start":{"line":117,"column":0},"end":{"line":117,"column":20}},"117":{"start":{"line":118,"column":0},"end":{"line":118,"column":16}},"118":{"start":{"line":119,"column":0},"end":{"line":119,"column":82}},"120":{"start":{"line":121,"column":0},"end":{"line":121,"column":23}},"121":{"start":{"line":122,"column":0},"end":{"line":122,"column":16}},"122":{"start":{"line":123,"column":0},"end":{"line":123,"column":22}},"123":{"start":{"line":124,"column":0},"end":{"line":124,"column":21}},"124":{"start":{"line":125,"column":0},"end":{"line":125,"column":70}},"125":{"start":{"line":126,"column":0},"end":{"line":126,"column":81}},"126":{"start":{"line":127,"column":0},"end":{"line":127,"column":22}},"127":{"start":{"line":128,"column":0},"end":{"line":128,"column":13}},"129":{"start":{"line":130,"column":0},"end":{"line":130,"column":1}},"143":{"start":{"line":144,"column":0},"end":{"line":144,"column":34}},"144":{"start":{"line":145,"column":0},"end":{"line":145,"column":16}},"145":{"start":{"line":146,"column":0},"end":{"line":146,"column":21}},"146":{"start":{"line":147,"column":0},"end":{"line":147,"column":16}},"147":{"start":{"line":148,"column":0},"end":{"line":148,"column":10}},"148":{"start":{"line":149,"column":0},"end":{"line":149,"column":16}},"149":{"start":{"line":150,"column":0},"end":{"line":150,"column":12}},"150":{"start":{"line":151,"column":0},"end":{"line":151,"column":10}},"151":{"start":{"line":152,"column":0},"end":{"line":152,"column":19}},"152":{"start":{"line":153,"column":0},"end":{"line":153,"column":27}},"153":{"start":{"line":154,"column":0},"end":{"line":154,"column":93}},"154":{"start":{"line":155,"column":0},"end":{"line":155,"column":68}},"155":{"start":{"line":156,"column":0},"end":{"line":156,"column":56}},"156":{"start":{"line":157,"column":0},"end":{"line":157,"column":66}},"157":{"start":{"line":158,"column":0},"end":{"line":158,"column":60}},"158":{"start":{"line":159,"column":0},"end":{"line":159,"column":72}},"159":{"start":{"line":160,"column":0},"end":{"line":160,"column":56}},"161":{"start":{"line":162,"column":0},"end":{"line":162,"column":80}},"162":{"start":{"line":163,"column":0},"end":{"line":163,"column":47}},"164":{"start":{"line":165,"column":0},"end":{"line":165,"column":52}},"165":{"start":{"line":166,"column":0},"end":{"line":166,"column":23}},"166":{"start":{"line":167,"column":0},"end":{"line":167,"column":66}},"167":{"start":{"line":168,"column":0},"end":{"line":168,"column":44}},"168":{"start":{"line":169,"column":0},"end":{"line":169,"column":24}},"169":{"start":{"line":170,"column":0},"end":{"line":170,"column":25}},"170":{"start":{"line":171,"column":0},"end":{"line":171,"column":4}},"172":{"start":{"line":173,"column":0},"end":{"line":173,"column":37}},"173":{"start":{"line":174,"column":0},"end":{"line":174,"column":26}},"174":{"start":{"line":175,"column":0},"end":{"line":175,"column":28}},"175":{"start":{"line":176,"column":0},"end":{"line":176,"column":26}},"176":{"start":{"line":177,"column":0},"end":{"line":177,"column":20}},"177":{"start":{"line":178,"column":0},"end":{"line":178,"column":9}},"178":{"start":{"line":179,"column":0},"end":{"line":179,"column":27}},"179":{"start":{"line":180,"column":0},"end":{"line":180,"column":53}},"180":{"start":{"line":181,"column":0},"end":{"line":181,"column":30}},"181":{"start":{"line":182,"column":0},"end":{"line":182,"column":51}},"182":{"start":{"line":183,"column":0},"end":{"line":183,"column":22}},"183":{"start":{"line":184,"column":0},"end":{"line":184,"column":27}},"184":{"start":{"line":185,"column":0},"end":{"line":185,"column":27}},"185":{"start":{"line":186,"column":0},"end":{"line":186,"column":38}},"186":{"start":{"line":187,"column":0},"end":{"line":187,"column":19}},"187":{"start":{"line":188,"column":0},"end":{"line":188,"column":81}},"188":{"start":{"line":189,"column":0},"end":{"line":189,"column":37}},"189":{"start":{"line":190,"column":0},"end":{"line":190,"column":25}},"190":{"start":{"line":191,"column":0},"end":{"line":191,"column":23}},"191":{"start":{"line":192,"column":0},"end":{"line":192,"column":5}},"192":{"start":{"line":193,"column":0},"end":{"line":193,"column":4}},"194":{"start":{"line":195,"column":0},"end":{"line":195,"column":29}},"195":{"start":{"line":196,"column":0},"end":{"line":196,"column":22}},"196":{"start":{"line":197,"column":0},"end":{"line":197,"column":26}},"197":{"start":{"line":198,"column":0},"end":{"line":198,"column":20}},"198":{"start":{"line":199,"column":0},"end":{"line":199,"column":4}},"200":{"start":{"line":201,"column":0},"end":{"line":201,"column":29}},"201":{"start":{"line":202,"column":0},"end":{"line":202,"column":22}},"202":{"start":{"line":203,"column":0},"end":{"line":203,"column":63}},"203":{"start":{"line":204,"column":0},"end":{"line":204,"column":24}},"204":{"start":{"line":205,"column":0},"end":{"line":205,"column":26}},"205":{"start":{"line":206,"column":0},"end":{"line":206,"column":20}},"206":{"start":{"line":207,"column":0},"end":{"line":207,"column":26}},"207":{"start":{"line":208,"column":0},"end":{"line":208,"column":4}},"209":{"start":{"line":210,"column":0},"end":{"line":210,"column":49}},"211":{"start":{"line":212,"column":0},"end":{"line":212,"column":10}},"212":{"start":{"line":213,"column":0},"end":{"line":213,"column":9}},"214":{"start":{"line":215,"column":0},"end":{"line":215,"column":26}},"215":{"start":{"line":216,"column":0},"end":{"line":216,"column":49}},"217":{"start":{"line":218,"column":0},"end":{"line":218,"column":16}},"221":{"start":{"line":222,"column":0},"end":{"line":222,"column":50}},"222":{"start":{"line":223,"column":0},"end":{"line":223,"column":14}},"223":{"start":{"line":224,"column":0},"end":{"line":224,"column":35}},"224":{"start":{"line":225,"column":0},"end":{"line":225,"column":24}},"225":{"start":{"line":226,"column":0},"end":{"line":226,"column":18}},"226":{"start":{"line":227,"column":0},"end":{"line":227,"column":34}},"227":{"start":{"line":228,"column":0},"end":{"line":228,"column":84}},"228":{"start":{"line":229,"column":0},"end":{"line":229,"column":40}},"229":{"start":{"line":230,"column":0},"end":{"line":230,"column":82}},"230":{"start":{"line":231,"column":0},"end":{"line":231,"column":25}},"233":{"start":{"line":234,"column":0},"end":{"line":234,"column":31}},"234":{"start":{"line":235,"column":0},"end":{"line":235,"column":47}},"235":{"start":{"line":236,"column":0},"end":{"line":236,"column":48}},"236":{"start":{"line":237,"column":0},"end":{"line":237,"column":16}},"237":{"start":{"line":238,"column":0},"end":{"line":238,"column":77}},"238":{"start":{"line":239,"column":0},"end":{"line":239,"column":31}},"239":{"start":{"line":240,"column":0},"end":{"line":240,"column":41}},"240":{"start":{"line":241,"column":0},"end":{"line":241,"column":65}},"241":{"start":{"line":242,"column":0},"end":{"line":242,"column":15}},"243":{"start":{"line":244,"column":0},"end":{"line":244,"column":18}},"245":{"start":{"line":246,"column":0},"end":{"line":246,"column":18}},"246":{"start":{"line":247,"column":0},"end":{"line":247,"column":16}},"250":{"start":{"line":251,"column":0},"end":{"line":251,"column":32}},"251":{"start":{"line":252,"column":0},"end":{"line":252,"column":69}},"252":{"start":{"line":253,"column":0},"end":{"line":253,"column":29}},"253":{"start":{"line":254,"column":0},"end":{"line":254,"column":22}},"254":{"start":{"line":255,"column":0},"end":{"line":255,"column":47}},"255":{"start":{"line":256,"column":0},"end":{"line":256,"column":27}},"256":{"start":{"line":257,"column":0},"end":{"line":257,"column":33}},"257":{"start":{"line":258,"column":0},"end":{"line":258,"column":88}},"258":{"start":{"line":259,"column":0},"end":{"line":259,"column":34}},"259":{"start":{"line":260,"column":0},"end":{"line":260,"column":105}},"260":{"start":{"line":261,"column":0},"end":{"line":261,"column":75}},"261":{"start":{"line":262,"column":0},"end":{"line":262,"column":67}},"262":{"start":{"line":263,"column":0},"end":{"line":263,"column":23}},"263":{"start":{"line":264,"column":0},"end":{"line":264,"column":26}},"264":{"start":{"line":265,"column":0},"end":{"line":265,"column":14}},"267":{"start":{"line":268,"column":0},"end":{"line":268,"column":31}},"268":{"start":{"line":269,"column":0},"end":{"line":269,"column":70}},"269":{"start":{"line":270,"column":0},"end":{"line":270,"column":85}},"270":{"start":{"line":271,"column":0},"end":{"line":271,"column":23}},"271":{"start":{"line":272,"column":0},"end":{"line":272,"column":27}},"272":{"start":{"line":273,"column":0},"end":{"line":273,"column":38}},"273":{"start":{"line":274,"column":0},"end":{"line":274,"column":32}},"274":{"start":{"line":275,"column":0},"end":{"line":275,"column":83}},"275":{"start":{"line":276,"column":0},"end":{"line":276,"column":81}},"276":{"start":{"line":277,"column":0},"end":{"line":277,"column":87}},"277":{"start":{"line":278,"column":0},"end":{"line":278,"column":73}},"278":{"start":{"line":279,"column":0},"end":{"line":279,"column":46}},"279":{"start":{"line":280,"column":0},"end":{"line":280,"column":20}},"280":{"start":{"line":281,"column":0},"end":{"line":281,"column":19}},"281":{"start":{"line":282,"column":0},"end":{"line":282,"column":20}},"284":{"start":{"line":285,"column":0},"end":{"line":285,"column":72}},"285":{"start":{"line":286,"column":0},"end":{"line":286,"column":21}},"286":{"start":{"line":287,"column":0},"end":{"line":287,"column":29}},"287":{"start":{"line":288,"column":0},"end":{"line":288,"column":33}},"288":{"start":{"line":289,"column":0},"end":{"line":289,"column":69}},"290":{"start":{"line":291,"column":0},"end":{"line":291,"column":33}},"291":{"start":{"line":292,"column":0},"end":{"line":292,"column":78}},"292":{"start":{"line":293,"column":0},"end":{"line":293,"column":66}},"294":{"start":{"line":295,"column":0},"end":{"line":295,"column":24}},"295":{"start":{"line":296,"column":0},"end":{"line":296,"column":33}},"296":{"start":{"line":297,"column":0},"end":{"line":297,"column":23}},"297":{"start":{"line":298,"column":0},"end":{"line":298,"column":38}},"298":{"start":{"line":299,"column":0},"end":{"line":299,"column":84}},"300":{"start":{"line":301,"column":0},"end":{"line":301,"column":18}},"301":{"start":{"line":302,"column":0},"end":{"line":302,"column":18}},"302":{"start":{"line":303,"column":0},"end":{"line":303,"column":14}},"306":{"start":{"line":307,"column":0},"end":{"line":307,"column":32}},"307":{"start":{"line":308,"column":0},"end":{"line":308,"column":62}},"308":{"start":{"line":309,"column":0},"end":{"line":309,"column":96}},"309":{"start":{"line":310,"column":0},"end":{"line":310,"column":61}},"310":{"start":{"line":311,"column":0},"end":{"line":311,"column":62}},"311":{"start":{"line":312,"column":0},"end":{"line":312,"column":23}},"312":{"start":{"line":313,"column":0},"end":{"line":313,"column":94}},"313":{"start":{"line":314,"column":0},"end":{"line":314,"column":16}},"317":{"start":{"line":318,"column":0},"end":{"line":318,"column":20}},"318":{"start":{"line":319,"column":0},"end":{"line":319,"column":26}},"319":{"start":{"line":320,"column":0},"end":{"line":320,"column":34}},"320":{"start":{"line":321,"column":0},"end":{"line":321,"column":25}},"321":{"start":{"line":322,"column":0},"end":{"line":322,"column":46}},"322":{"start":{"line":323,"column":0},"end":{"line":323,"column":46}},"323":{"start":{"line":324,"column":0},"end":{"line":324,"column":8}},"326":{"start":{"line":327,"column":0},"end":{"line":327,"column":31}},"327":{"start":{"line":328,"column":0},"end":{"line":328,"column":26}},"328":{"start":{"line":329,"column":0},"end":{"line":329,"column":34}},"329":{"start":{"line":330,"column":0},"end":{"line":330,"column":25}},"330":{"start":{"line":331,"column":0},"end":{"line":331,"column":36}},"331":{"start":{"line":332,"column":0},"end":{"line":332,"column":29}},"332":{"start":{"line":333,"column":0},"end":{"line":333,"column":8}},"333":{"start":{"line":334,"column":0},"end":{"line":334,"column":10}},"335":{"start":{"line":336,"column":0},"end":{"line":336,"column":1}},"337":{"start":{"line":338,"column":0},"end":{"line":338,"column":32}}},"s":{"0":1,"42":23,"43":23,"44":23,"45":23,"46":23,"47":23,"48":16,"49":16,"53":13,"55":13,"56":13,"58":7,"59":13,"60":7,"61":7,"65":1,"66":1,"67":1,"68":1,"69":1,"70":1,"71":1,"72":1,"74":1,"75":1,"76":1,"77":1,"78":1,"79":1,"80":1,"92":118,"93":118,"94":118,"95":118,"96":118,"97":118,"98":118,"99":118,"100":118,"101":118,"102":118,"103":118,"104":118,"105":118,"106":118,"107":118,"108":118,"109":118,"110":118,"111":118,"112":118,"113":118,"114":118,"115":118,"116":118,"117":118,"118":118,"120":118,"121":118,"122":118,"123":118,"124":118,"125":118,"126":118,"127":118,"129":118,"143":1,"144":118,"145":118,"146":118,"147":118,"148":118,"149":118,"150":118,"151":118,"152":118,"153":118,"154":118,"155":118,"156":118,"157":118,"158":118,"159":118,"161":118,"162":118,"164":118,"165":23,"166":23,"167":23,"168":16,"169":16,"170":23,"172":118,"173":13,"174":13,"175":13,"176":13,"177":13,"178":13,"179":13,"180":7,"181":7,"182":7,"183":7,"184":7,"185":13,"186":13,"187":6,"188":6,"189":6,"190":6,"191":6,"192":13,"194":118,"195":2,"196":2,"197":2,"198":2,"200":118,"201":2,"202":2,"203":2,"204":2,"205":2,"206":2,"207":2,"209":118,"211":118,"212":118,"214":118,"215":2,"217":2,"221":118,"222":33,"223":33,"224":33,"225":33,"226":33,"227":6,"228":27,"229":7,"230":20,"233":33,"234":33,"235":33,"236":7,"237":7,"238":7,"239":7,"240":7,"241":7,"243":7,"245":33,"246":33,"250":118,"251":111,"252":111,"253":111,"254":111,"255":111,"256":111,"257":111,"258":111,"259":111,"260":111,"261":111,"262":111,"263":111,"264":111,"267":111,"268":80,"269":80,"270":240,"271":240,"272":240,"273":240,"274":240,"275":240,"276":240,"277":240,"278":240,"279":240,"280":80,"281":80,"284":111,"285":111,"286":111,"287":111,"288":111,"290":111,"291":20,"292":20,"294":20,"295":91,"296":111,"297":111,"298":6,"300":111,"301":111,"302":111,"306":118,"307":7,"308":7,"309":7,"310":7,"311":7,"312":7,"313":7,"317":118,"318":118,"319":118,"320":118,"321":118,"322":118,"323":118,"326":118,"327":118,"328":118,"329":118,"330":118,"331":118,"332":118,"333":118,"335":118,"337":1},"branchMap":{"0":{"type":"branch","line":43,"loc":{"start":{"line":43,"column":0},"end":{"line":50,"column":1}},"locations":[{"start":{"line":43,"column":0},"end":{"line":50,"column":1}}]},"1":{"type":"branch","line":45,"loc":{"start":{"line":45,"column":16},"end":{"line":45,"column":35}},"locations":[{"start":{"line":45,"column":16},"end":{"line":45,"column":35}}]},"2":{"type":"branch","line":45,"loc":{"start":{"line":45,"column":35},"end":{"line":45,"column":73}},"locations":[{"start":{"line":45,"column":35},"end":{"line":45,"column":73}}]},"3":{"type":"branch","line":45,"loc":{"start":{"line":45,"column":42},"end":{"line":46,"column":25}},"locations":[{"start":{"line":45,"column":42},"end":{"line":46,"column":25}}]},"4":{"type":"branch","line":46,"loc":{"start":{"line":46,"column":18},"end":{"line":46,"column":58}},"locations":[{"start":{"line":46,"column":18},"end":{"line":46,"column":58}}]},"5":{"type":"branch","line":46,"loc":{"start":{"line":46,"column":25},"end":{"line":47,"column":26}},"locations":[{"start":{"line":46,"column":25},"end":{"line":47,"column":26}}]},"6":{"type":"branch","line":47,"loc":{"start":{"line":47,"column":19},"end":{"line":47,"column":64}},"locations":[{"start":{"line":47,"column":19},"end":{"line":47,"column":64}}]},"7":{"type":"branch","line":47,"loc":{"start":{"line":47,"column":56},"end":{"line":48,"column":26}},"locations":[{"start":{"line":47,"column":56},"end":{"line":48,"column":26}}]},"8":{"type":"branch","line":48,"loc":{"start":{"line":48,"column":19},"end":{"line":48,"column":64}},"locations":[{"start":{"line":48,"column":19},"end":{"line":48,"column":64}}]},"9":{"type":"branch","line":48,"loc":{"start":{"line":48,"column":56},"end":{"line":50,"column":1}},"locations":[{"start":{"line":48,"column":56},"end":{"line":50,"column":1}}]},"10":{"type":"branch","line":54,"loc":{"start":{"line":54,"column":0},"end":{"line":62,"column":1}},"locations":[{"start":{"line":54,"column":0},"end":{"line":62,"column":1}}]},"11":{"type":"branch","line":57,"loc":{"start":{"line":57,"column":28},"end":{"line":57,"column":88}},"locations":[{"start":{"line":57,"column":28},"end":{"line":57,"column":88}}]},"12":{"type":"branch","line":57,"loc":{"start":{"line":57,"column":86},"end":{"line":60,"column":34}},"locations":[{"start":{"line":57,"column":86},"end":{"line":60,"column":34}}]},"13":{"type":"branch","line":60,"loc":{"start":{"line":60,"column":28},"end":{"line":60,"column":85}},"locations":[{"start":{"line":60,"column":28},"end":{"line":60,"column":85}}]},"14":{"type":"branch","line":60,"loc":{"start":{"line":60,"column":83},"end":{"line":62,"column":1}},"locations":[{"start":{"line":60,"column":83},"end":{"line":62,"column":1}}]},"15":{"type":"branch","line":56,"loc":{"start":{"line":56,"column":20},"end":{"line":56,"column":46}},"locations":[{"start":{"line":56,"column":20},"end":{"line":56,"column":46}}]},"16":{"type":"branch","line":59,"loc":{"start":{"line":59,"column":20},"end":{"line":59,"column":45}},"locations":[{"start":{"line":59,"column":20},"end":{"line":59,"column":45}}]},"17":{"type":"branch","line":93,"loc":{"start":{"line":93,"column":0},"end":{"line":130,"column":1}},"locations":[{"start":{"line":93,"column":0},"end":{"line":130,"column":1}}]},"18":{"type":"branch","line":144,"loc":{"start":{"line":144,"column":7},"end":{"line":336,"column":1}},"locations":[{"start":{"line":144,"column":7},"end":{"line":336,"column":1}}]},"19":{"type":"branch","line":154,"loc":{"start":{"line":154,"column":49},"end":{"line":154,"column":89}},"locations":[{"start":{"line":154,"column":49},"end":{"line":154,"column":89}}]},"20":{"type":"branch","line":154,"loc":{"start":{"line":154,"column":85},"end":{"line":154,"column":91}},"locations":[{"start":{"line":154,"column":85},"end":{"line":154,"column":91}}]},"21":{"type":"branch","line":215,"loc":{"start":{"line":215,"column":8},"end":{"line":218,"column":16}},"locations":[{"start":{"line":215,"column":8},"end":{"line":218,"column":16}}]},"22":{"type":"branch","line":222,"loc":{"start":{"line":222,"column":7},"end":{"line":222,"column":50}},"locations":[{"start":{"line":222,"column":7},"end":{"line":222,"column":50}}]},"23":{"type":"branch","line":222,"loc":{"start":{"line":222,"column":44},"end":{"line":247,"column":16}},"locations":[{"start":{"line":222,"column":44},"end":{"line":247,"column":16}}]},"24":{"type":"branch","line":227,"loc":{"start":{"line":227,"column":23},"end":{"line":228,"column":84}},"locations":[{"start":{"line":227,"column":23},"end":{"line":228,"column":84}}]},"25":{"type":"branch","line":228,"loc":{"start":{"line":228,"column":75},"end":{"line":231,"column":25}},"locations":[{"start":{"line":228,"column":75},"end":{"line":231,"column":25}}]},"26":{"type":"branch","line":229,"loc":{"start":{"line":229,"column":27},"end":{"line":230,"column":82}},"locations":[{"start":{"line":229,"column":27},"end":{"line":230,"column":82}}]},"27":{"type":"branch","line":230,"loc":{"start":{"line":230,"column":73},"end":{"line":231,"column":25}},"locations":[{"start":{"line":230,"column":73},"end":{"line":231,"column":25}}]},"28":{"type":"branch","line":236,"loc":{"start":{"line":236,"column":13},"end":{"line":236,"column":48}},"locations":[{"start":{"line":236,"column":13},"end":{"line":236,"column":48}}]},"29":{"type":"branch","line":236,"loc":{"start":{"line":236,"column":34},"end":{"line":244,"column":18}},"locations":[{"start":{"line":236,"column":34},"end":{"line":244,"column":18}}]},"30":{"type":"branch","line":251,"loc":{"start":{"line":251,"column":18},"end":{"line":303,"column":14}},"locations":[{"start":{"line":251,"column":18},"end":{"line":303,"column":14}}]},"31":{"type":"branch","line":260,"loc":{"start":{"line":260,"column":26},"end":{"line":260,"column":105}},"locations":[{"start":{"line":260,"column":26},"end":{"line":260,"column":105}}]},"32":{"type":"branch","line":262,"loc":{"start":{"line":262,"column":24},"end":{"line":262,"column":58}},"locations":[{"start":{"line":262,"column":24},"end":{"line":262,"column":58}}]},"33":{"type":"branch","line":262,"loc":{"start":{"line":262,"column":41},"end":{"line":262,"column":67}},"locations":[{"start":{"line":262,"column":41},"end":{"line":262,"column":67}}]},"34":{"type":"branch","line":268,"loc":{"start":{"line":268,"column":13},"end":{"line":282,"column":20}},"locations":[{"start":{"line":268,"column":13},"end":{"line":282,"column":20}}]},"35":{"type":"branch","line":289,"loc":{"start":{"line":289,"column":26},"end":{"line":289,"column":60}},"locations":[{"start":{"line":289,"column":26},"end":{"line":289,"column":60}}]},"36":{"type":"branch","line":289,"loc":{"start":{"line":289,"column":43},"end":{"line":289,"column":69}},"locations":[{"start":{"line":289,"column":43},"end":{"line":289,"column":69}}]},"37":{"type":"branch","line":291,"loc":{"start":{"line":291,"column":17},"end":{"line":295,"column":24}},"locations":[{"start":{"line":291,"column":17},"end":{"line":295,"column":24}}]},"38":{"type":"branch","line":295,"loc":{"start":{"line":295,"column":18},"end":{"line":296,"column":33}},"locations":[{"start":{"line":295,"column":18},"end":{"line":296,"column":33}}]},"39":{"type":"branch","line":298,"loc":{"start":{"line":298,"column":26},"end":{"line":299,"column":84}},"locations":[{"start":{"line":298,"column":26},"end":{"line":299,"column":84}}]},"40":{"type":"branch","line":307,"loc":{"start":{"line":307,"column":18},"end":{"line":314,"column":16}},"locations":[{"start":{"line":307,"column":18},"end":{"line":314,"column":16}}]},"41":{"type":"branch","line":320,"loc":{"start":{"line":320,"column":16},"end":{"line":320,"column":34}},"locations":[{"start":{"line":320,"column":16},"end":{"line":320,"column":34}}]},"42":{"type":"branch","line":329,"loc":{"start":{"line":329,"column":16},"end":{"line":329,"column":34}},"locations":[{"start":{"line":329,"column":16},"end":{"line":329,"column":34}}]},"43":{"type":"branch","line":331,"loc":{"start":{"line":331,"column":16},"end":{"line":331,"column":36}},"locations":[{"start":{"line":331,"column":16},"end":{"line":331,"column":36}}]},"44":{"type":"branch","line":165,"loc":{"start":{"line":165,"column":27},"end":{"line":171,"column":4}},"locations":[{"start":{"line":165,"column":27},"end":{"line":171,"column":4}}]},"45":{"type":"branch","line":168,"loc":{"start":{"line":168,"column":13},"end":{"line":168,"column":44}},"locations":[{"start":{"line":168,"column":13},"end":{"line":168,"column":44}}]},"46":{"type":"branch","line":168,"loc":{"start":{"line":168,"column":43},"end":{"line":170,"column":25}},"locations":[{"start":{"line":168,"column":43},"end":{"line":170,"column":25}}]},"47":{"type":"branch","line":173,"loc":{"start":{"line":173,"column":24},"end":{"line":193,"column":4}},"locations":[{"start":{"line":173,"column":24},"end":{"line":193,"column":4}}]},"48":{"type":"branch","line":180,"loc":{"start":{"line":180,"column":51},"end":{"line":186,"column":18}},"locations":[{"start":{"line":180,"column":51},"end":{"line":186,"column":18}}]},"49":{"type":"branch","line":186,"loc":{"start":{"line":186,"column":6},"end":{"line":186,"column":38}},"locations":[{"start":{"line":186,"column":6},"end":{"line":186,"column":38}}]},"50":{"type":"branch","line":187,"loc":{"start":{"line":187,"column":4},"end":{"line":192,"column":5}},"locations":[{"start":{"line":187,"column":4},"end":{"line":192,"column":5}}]},"51":{"type":"branch","line":188,"loc":{"start":{"line":188,"column":43},"end":{"line":188,"column":81}},"locations":[{"start":{"line":188,"column":43},"end":{"line":188,"column":81}}]},"52":{"type":"branch","line":191,"loc":{"start":{"line":191,"column":6},"end":{"line":191,"column":23}},"locations":[{"start":{"line":191,"column":6},"end":{"line":191,"column":23}}]},"53":{"type":"branch","line":182,"loc":{"start":{"line":182,"column":24},"end":{"line":182,"column":49}},"locations":[{"start":{"line":182,"column":24},"end":{"line":182,"column":49}}]},"54":{"type":"branch","line":195,"loc":{"start":{"line":195,"column":22},"end":{"line":199,"column":4}},"locations":[{"start":{"line":195,"column":22},"end":{"line":199,"column":4}}]},"55":{"type":"branch","line":201,"loc":{"start":{"line":201,"column":22},"end":{"line":208,"column":4}},"locations":[{"start":{"line":201,"column":22},"end":{"line":208,"column":4}}]},"56":{"type":"branch","line":203,"loc":{"start":{"line":203,"column":55},"end":{"line":203,"column":61}},"locations":[{"start":{"line":203,"column":55},"end":{"line":203,"column":61}}]},"57":{"type":"branch","line":258,"loc":{"start":{"line":258,"column":24},"end":{"line":258,"column":88}},"locations":[{"start":{"line":258,"column":24},"end":{"line":258,"column":88}}]},"58":{"type":"branch","line":270,"loc":{"start":{"line":270,"column":77},"end":{"line":280,"column":20}},"locations":[{"start":{"line":270,"column":77},"end":{"line":280,"column":20}}]},"59":{"type":"branch","line":275,"loc":{"start":{"line":275,"column":57},"end":{"line":275,"column":72}},"locations":[{"start":{"line":275,"column":57},"end":{"line":275,"column":72}}]},"60":{"type":"branch","line":275,"loc":{"start":{"line":275,"column":61},"end":{"line":275,"column":83}},"locations":[{"start":{"line":275,"column":61},"end":{"line":275,"column":83}}]},"61":{"type":"branch","line":276,"loc":{"start":{"line":276,"column":55},"end":{"line":276,"column":71}},"locations":[{"start":{"line":276,"column":55},"end":{"line":276,"column":71}}]},"62":{"type":"branch","line":276,"loc":{"start":{"line":276,"column":59},"end":{"line":276,"column":81}},"locations":[{"start":{"line":276,"column":59},"end":{"line":276,"column":81}}]},"63":{"type":"branch","line":278,"loc":{"start":{"line":278,"column":30},"end":{"line":278,"column":64}},"locations":[{"start":{"line":278,"column":30},"end":{"line":278,"column":64}}]},"64":{"type":"branch","line":278,"loc":{"start":{"line":278,"column":47},"end":{"line":278,"column":73}},"locations":[{"start":{"line":278,"column":47},"end":{"line":278,"column":73}}]},"65":{"type":"branch","line":277,"loc":{"start":{"line":277,"column":29},"end":{"line":277,"column":87}},"locations":[{"start":{"line":277,"column":29},"end":{"line":277,"column":87}}]},"66":{"type":"branch","line":322,"loc":{"start":{"line":322,"column":19},"end":{"line":322,"column":46}},"locations":[{"start":{"line":322,"column":19},"end":{"line":322,"column":46}}]},"67":{"type":"branch","line":323,"loc":{"start":{"line":323,"column":18},"end":{"line":323,"column":46}},"locations":[{"start":{"line":323,"column":18},"end":{"line":323,"column":46}}]}},"b":{"0":[23],"1":[22],"2":[1],"3":[22],"4":[1],"5":[21],"6":[2],"7":[19],"8":[3],"9":[16],"10":[13],"11":[6],"12":[7],"13":[0],"14":[7],"15":[13],"16":[7],"17":[118],"18":[118],"19":[87],"20":[31],"21":[2],"22":[33],"23":[33],"24":[6],"25":[27],"26":[7],"27":[20],"28":[7],"29":[7],"30":[111],"31":[104],"32":[91],"33":[89],"34":[80],"35":[91],"36":[89],"37":[20],"38":[91],"39":[6],"40":[7],"41":[19],"42":[19],"43":[111],"44":[23],"45":[7],"46":[16],"47":[13],"48":[7],"49":[3],"50":[6],"51":[0],"52":[2],"53":[7],"54":[2],"55":[2],"56":[0],"57":[20],"58":[240],"59":[69],"60":[171],"61":[69],"62":[171],"63":[180],"64":[180],"65":[2],"66":[13],"67":[1]},"fnMap":{"0":{"name":"validateAmount","decl":{"start":{"line":43,"column":0},"end":{"line":50,"column":1}},"loc":{"start":{"line":43,"column":0},"end":{"line":50,"column":1}},"line":43},"1":{"name":"signAndSubmit","decl":{"start":{"line":54,"column":0},"end":{"line":62,"column":1}},"loc":{"start":{"line":54,"column":0},"end":{"line":62,"column":1}},"line":54},"2":{"name":"ConfirmDialog","decl":{"start":{"line":93,"column":0},"end":{"line":130,"column":1}},"loc":{"start":{"line":93,"column":0},"end":{"line":130,"column":1}},"line":93},"3":{"name":"ContributionFlow","decl":{"start":{"line":144,"column":7},"end":{"line":336,"column":1}},"loc":{"start":{"line":144,"column":7},"end":{"line":336,"column":1}},"line":144},"4":{"name":"handleSubmitForm","decl":{"start":{"line":165,"column":27},"end":{"line":171,"column":4}},"loc":{"start":{"line":165,"column":27},"end":{"line":171,"column":4}},"line":165},"5":{"name":"handleConfirm","decl":{"start":{"line":173,"column":24},"end":{"line":193,"column":4}},"loc":{"start":{"line":173,"column":24},"end":{"line":193,"column":4}},"line":173},"6":{"name":"handleRetry","decl":{"start":{"line":195,"column":22},"end":{"line":199,"column":4}},"loc":{"start":{"line":195,"column":22},"end":{"line":199,"column":4}},"line":195},"7":{"name":"handleReset","decl":{"start":{"line":201,"column":22},"end":{"line":208,"column":4}},"loc":{"start":{"line":201,"column":22},"end":{"line":208,"column":4}},"line":201},"8":{"name":"onChange","decl":{"start":{"line":258,"column":24},"end":{"line":258,"column":88}},"loc":{"start":{"line":258,"column":24},"end":{"line":258,"column":88}},"line":258},"9":{"name":"onClick","decl":{"start":{"line":277,"column":29},"end":{"line":277,"column":87}},"loc":{"start":{"line":277,"column":29},"end":{"line":277,"column":87}},"line":277},"10":{"name":"onConfirm","decl":{"start":{"line":322,"column":19},"end":{"line":322,"column":46}},"loc":{"start":{"line":322,"column":19},"end":{"line":322,"column":46}},"line":322},"11":{"name":"onCancel","decl":{"start":{"line":323,"column":18},"end":{"line":323,"column":46}},"loc":{"start":{"line":323,"column":18},"end":{"line":323,"column":46}},"line":323}},"f":{"0":23,"1":13,"2":118,"3":118,"4":23,"5":13,"6":2,"7":2,"8":20,"9":2,"10":13,"11":1}} +,"/home/kimani/drips/Stellar-Save/frontend/src/components/GroupCard.tsx": {"path":"/home/kimani/drips/Stellar-Save/frontend/src/components/GroupCard.tsx","all":false,"statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":1,"column":40}},"54":{"start":{"line":55,"column":0},"end":{"line":55,"column":35}},"56":{"start":{"line":57,"column":0},"end":{"line":57,"column":45}},"57":{"start":{"line":58,"column":0},"end":{"line":58,"column":62}},"58":{"start":{"line":59,"column":0},"end":{"line":59,"column":29}},"59":{"start":{"line":60,"column":0},"end":{"line":60,"column":29}},"60":{"start":{"line":61,"column":0},"end":{"line":61,"column":5}},"61":{"start":{"line":62,"column":0},"end":{"line":62,"column":1}},"63":{"start":{"line":64,"column":0},"end":{"line":64,"column":27}},"64":{"start":{"line":65,"column":0},"end":{"line":65,"column":25}},"65":{"start":{"line":66,"column":0},"end":{"line":66,"column":23}},"66":{"start":{"line":67,"column":0},"end":{"line":67,"column":28}},"67":{"start":{"line":68,"column":0},"end":{"line":68,"column":16}},"68":{"start":{"line":69,"column":0},"end":{"line":69,"column":56}},"69":{"start":{"line":70,"column":0},"end":{"line":70,"column":22}},"70":{"start":{"line":71,"column":0},"end":{"line":71,"column":72}},"71":{"start":{"line":72,"column":0},"end":{"line":72,"column":32}},"72":{"start":{"line":73,"column":0},"end":{"line":73,"column":1}},"74":{"start":{"line":75,"column":0},"end":{"line":75,"column":60}},"75":{"start":{"line":76,"column":0},"end":{"line":76,"column":24}},"76":{"start":{"line":77,"column":0},"end":{"line":77,"column":95}},"77":{"start":{"line":78,"column":0},"end":{"line":78,"column":1}},"97":{"start":{"line":98,"column":0},"end":{"line":98,"column":22}},"98":{"start":{"line":99,"column":0},"end":{"line":99,"column":10}},"99":{"start":{"line":100,"column":0},"end":{"line":100,"column":12}},"100":{"start":{"line":101,"column":0},"end":{"line":101,"column":14}},"101":{"start":{"line":102,"column":0},"end":{"line":102,"column":21}},"102":{"start":{"line":103,"column":0},"end":{"line":103,"column":9}},"103":{"start":{"line":104,"column":0},"end":{"line":104,"column":15}},"104":{"start":{"line":105,"column":0},"end":{"line":105,"column":17}},"105":{"start":{"line":106,"column":0},"end":{"line":106,"column":14}},"106":{"start":{"line":107,"column":0},"end":{"line":107,"column":11}},"107":{"start":{"line":108,"column":0},"end":{"line":108,"column":10}},"108":{"start":{"line":109,"column":0},"end":{"line":109,"column":16}},"109":{"start":{"line":110,"column":0},"end":{"line":110,"column":9}},"110":{"start":{"line":111,"column":0},"end":{"line":111,"column":17}},"111":{"start":{"line":112,"column":0},"end":{"line":112,"column":17}},"112":{"start":{"line":113,"column":0},"end":{"line":113,"column":70}},"114":{"start":{"line":115,"column":0},"end":{"line":115,"column":52}},"115":{"start":{"line":116,"column":0},"end":{"line":116,"column":60}},"116":{"start":{"line":117,"column":0},"end":{"line":117,"column":16}},"117":{"start":{"line":118,"column":0},"end":{"line":118,"column":4}},"119":{"start":{"line":120,"column":0},"end":{"line":120,"column":19}},"120":{"start":{"line":121,"column":0},"end":{"line":121,"column":6}},"121":{"start":{"line":122,"column":0},"end":{"line":122,"column":20}},"122":{"start":{"line":123,"column":0},"end":{"line":123,"column":42}},"123":{"start":{"line":124,"column":0},"end":{"line":124,"column":48}},"124":{"start":{"line":125,"column":0},"end":{"line":125,"column":14}},"127":{"start":{"line":128,"column":0},"end":{"line":128,"column":41}},"128":{"start":{"line":129,"column":0},"end":{"line":129,"column":57}},"129":{"start":{"line":130,"column":0},"end":{"line":130,"column":38}},"130":{"start":{"line":131,"column":0},"end":{"line":131,"column":12}},"132":{"start":{"line":133,"column":0},"end":{"line":133,"column":23}},"133":{"start":{"line":134,"column":0},"end":{"line":134,"column":48}},"134":{"start":{"line":135,"column":0},"end":{"line":135,"column":30}},"135":{"start":{"line":136,"column":0},"end":{"line":136,"column":14}},"138":{"start":{"line":139,"column":0},"end":{"line":139,"column":39}},"139":{"start":{"line":140,"column":0},"end":{"line":140,"column":42}},"140":{"start":{"line":141,"column":0},"end":{"line":141,"column":43}},"141":{"start":{"line":142,"column":0},"end":{"line":142,"column":71}},"142":{"start":{"line":143,"column":0},"end":{"line":143,"column":79}},"143":{"start":{"line":144,"column":0},"end":{"line":144,"column":16}},"144":{"start":{"line":145,"column":0},"end":{"line":145,"column":43}},"145":{"start":{"line":146,"column":0},"end":{"line":146,"column":66}},"146":{"start":{"line":147,"column":0},"end":{"line":147,"column":72}},"147":{"start":{"line":148,"column":0},"end":{"line":148,"column":16}},"148":{"start":{"line":149,"column":0},"end":{"line":149,"column":43}},"149":{"start":{"line":150,"column":0},"end":{"line":150,"column":64}},"150":{"start":{"line":151,"column":0},"end":{"line":151,"column":73}},"151":{"start":{"line":152,"column":0},"end":{"line":152,"column":16}},"152":{"start":{"line":153,"column":0},"end":{"line":153,"column":43}},"153":{"start":{"line":154,"column":0},"end":{"line":154,"column":70}},"154":{"start":{"line":155,"column":0},"end":{"line":155,"column":80}},"155":{"start":{"line":156,"column":0},"end":{"line":156,"column":42}},"156":{"start":{"line":157,"column":0},"end":{"line":157,"column":19}},"157":{"start":{"line":158,"column":0},"end":{"line":158,"column":16}},"158":{"start":{"line":159,"column":0},"end":{"line":159,"column":14}},"159":{"start":{"line":160,"column":0},"end":{"line":160,"column":12}},"161":{"start":{"line":162,"column":0},"end":{"line":162,"column":41}},"162":{"start":{"line":163,"column":0},"end":{"line":163,"column":27}},"163":{"start":{"line":164,"column":0},"end":{"line":164,"column":17}},"164":{"start":{"line":165,"column":0},"end":{"line":165,"column":31}},"165":{"start":{"line":166,"column":0},"end":{"line":166,"column":21}},"166":{"start":{"line":167,"column":0},"end":{"line":167,"column":70}},"167":{"start":{"line":168,"column":0},"end":{"line":168,"column":11}},"169":{"start":{"line":170,"column":0},"end":{"line":170,"column":19}},"171":{"start":{"line":172,"column":0},"end":{"line":172,"column":20}},"172":{"start":{"line":173,"column":0},"end":{"line":173,"column":17}},"173":{"start":{"line":174,"column":0},"end":{"line":174,"column":29}},"174":{"start":{"line":175,"column":0},"end":{"line":175,"column":21}},"175":{"start":{"line":176,"column":0},"end":{"line":176,"column":63}},"176":{"start":{"line":177,"column":0},"end":{"line":177,"column":11}},"178":{"start":{"line":179,"column":0},"end":{"line":179,"column":19}},"180":{"start":{"line":181,"column":0},"end":{"line":181,"column":12}},"181":{"start":{"line":182,"column":0},"end":{"line":182,"column":7}},"184":{"start":{"line":185,"column":0},"end":{"line":185,"column":16}},"185":{"start":{"line":186,"column":0},"end":{"line":186,"column":12}},"186":{"start":{"line":187,"column":0},"end":{"line":187,"column":11}},"187":{"start":{"line":188,"column":0},"end":{"line":188,"column":44}},"188":{"start":{"line":189,"column":0},"end":{"line":189,"column":27}},"189":{"start":{"line":190,"column":0},"end":{"line":190,"column":60}},"190":{"start":{"line":191,"column":0},"end":{"line":191,"column":33}},"192":{"start":{"line":193,"column":0},"end":{"line":193,"column":17}},"193":{"start":{"line":194,"column":0},"end":{"line":194,"column":13}},"195":{"start":{"line":196,"column":0},"end":{"line":196,"column":3}},"197":{"start":{"line":198,"column":0},"end":{"line":198,"column":10}},"198":{"start":{"line":199,"column":0},"end":{"line":199,"column":55}},"199":{"start":{"line":200,"column":0},"end":{"line":200,"column":15}},"200":{"start":{"line":201,"column":0},"end":{"line":201,"column":10}},"202":{"start":{"line":203,"column":0},"end":{"line":203,"column":1}},"216":{"start":{"line":217,"column":0},"end":{"line":217,"column":50}},"217":{"start":{"line":218,"column":0},"end":{"line":218,"column":83}},"220":{"start":{"line":221,"column":0},"end":{"line":221,"column":47}},"221":{"start":{"line":222,"column":0},"end":{"line":222,"column":39}},"222":{"start":{"line":223,"column":0},"end":{"line":223,"column":77}},"223":{"start":{"line":224,"column":0},"end":{"line":224,"column":25}},"224":{"start":{"line":225,"column":0},"end":{"line":225,"column":22}},"225":{"start":{"line":226,"column":0},"end":{"line":226,"column":5}},"227":{"start":{"line":228,"column":0},"end":{"line":228,"column":20}},"228":{"start":{"line":229,"column":0},"end":{"line":229,"column":48}},"230":{"start":{"line":231,"column":0},"end":{"line":231,"column":25}},"231":{"start":{"line":232,"column":0},"end":{"line":232,"column":14}},"232":{"start":{"line":233,"column":0},"end":{"line":233,"column":67}},"233":{"start":{"line":234,"column":0},"end":{"line":234,"column":46}},"234":{"start":{"line":235,"column":0},"end":{"line":235,"column":78}},"235":{"start":{"line":236,"column":0},"end":{"line":236,"column":14}},"236":{"start":{"line":237,"column":0},"end":{"line":237,"column":14}},"238":{"start":{"line":239,"column":0},"end":{"line":239,"column":5}},"240":{"start":{"line":241,"column":0},"end":{"line":241,"column":96}},"241":{"start":{"line":242,"column":0},"end":{"line":242,"column":79}},"243":{"start":{"line":244,"column":0},"end":{"line":244,"column":12}},"244":{"start":{"line":245,"column":0},"end":{"line":245,"column":18}},"245":{"start":{"line":246,"column":0},"end":{"line":246,"column":25}},"246":{"start":{"line":247,"column":0},"end":{"line":247,"column":29}},"247":{"start":{"line":248,"column":0},"end":{"line":248,"column":38}},"248":{"start":{"line":249,"column":0},"end":{"line":249,"column":38}},"249":{"start":{"line":250,"column":0},"end":{"line":250,"column":38}},"250":{"start":{"line":251,"column":0},"end":{"line":251,"column":40}},"251":{"start":{"line":252,"column":0},"end":{"line":252,"column":35}},"252":{"start":{"line":253,"column":0},"end":{"line":253,"column":38}},"253":{"start":{"line":254,"column":0},"end":{"line":254,"column":32}},"254":{"start":{"line":255,"column":0},"end":{"line":255,"column":31}},"255":{"start":{"line":256,"column":0},"end":{"line":256,"column":43}},"256":{"start":{"line":257,"column":0},"end":{"line":257,"column":29}},"257":{"start":{"line":258,"column":0},"end":{"line":258,"column":35}},"258":{"start":{"line":259,"column":0},"end":{"line":259,"column":8}},"260":{"start":{"line":261,"column":0},"end":{"line":261,"column":3}},"263":{"start":{"line":264,"column":0},"end":{"line":264,"column":42}},"264":{"start":{"line":265,"column":0},"end":{"line":265,"column":93}},"266":{"start":{"line":267,"column":0},"end":{"line":267,"column":10}},"267":{"start":{"line":268,"column":0},"end":{"line":268,"column":16}},"268":{"start":{"line":269,"column":0},"end":{"line":269,"column":25}},"269":{"start":{"line":270,"column":0},"end":{"line":270,"column":29}},"270":{"start":{"line":271,"column":0},"end":{"line":271,"column":33}},"271":{"start":{"line":272,"column":0},"end":{"line":272,"column":36}},"272":{"start":{"line":273,"column":0},"end":{"line":273,"column":35}},"273":{"start":{"line":274,"column":0},"end":{"line":274,"column":40}},"274":{"start":{"line":275,"column":0},"end":{"line":275,"column":39}},"275":{"start":{"line":276,"column":0},"end":{"line":276,"column":33}},"276":{"start":{"line":277,"column":0},"end":{"line":277,"column":27}},"277":{"start":{"line":278,"column":0},"end":{"line":278,"column":25}},"278":{"start":{"line":279,"column":0},"end":{"line":279,"column":37}},"279":{"start":{"line":280,"column":0},"end":{"line":280,"column":23}},"280":{"start":{"line":281,"column":0},"end":{"line":281,"column":29}},"281":{"start":{"line":282,"column":0},"end":{"line":282,"column":6}},"283":{"start":{"line":284,"column":0},"end":{"line":284,"column":1}}},"s":{"0":1,"54":1,"56":7,"57":7,"58":7,"59":7,"60":7,"61":7,"63":7,"64":7,"65":7,"66":7,"67":7,"68":7,"69":7,"70":7,"71":7,"72":7,"74":35,"75":35,"76":8,"77":8,"97":35,"98":35,"99":35,"100":35,"101":35,"102":35,"103":35,"104":35,"105":35,"106":35,"107":35,"108":35,"109":35,"110":35,"111":35,"112":35,"114":35,"115":1,"116":1,"117":1,"119":35,"120":35,"121":35,"122":8,"123":8,"124":8,"127":35,"128":35,"129":35,"130":35,"132":35,"133":8,"134":8,"135":8,"138":35,"139":35,"140":35,"141":35,"142":35,"143":35,"144":35,"145":35,"146":35,"147":35,"148":35,"149":35,"150":35,"151":35,"152":35,"153":35,"154":35,"155":35,"156":35,"157":35,"158":35,"159":35,"161":35,"162":35,"163":4,"164":4,"165":4,"166":4,"167":4,"169":4,"171":35,"172":5,"173":5,"174":5,"175":5,"176":5,"178":5,"180":35,"181":35,"184":35,"185":8,"186":8,"187":8,"188":8,"189":8,"190":8,"192":8,"193":8,"195":8,"197":27,"198":27,"199":27,"200":27,"202":27,"216":1,"217":49,"220":49,"221":49,"222":49,"223":49,"224":49,"225":49,"227":49,"228":21,"230":21,"231":3,"232":3,"233":3,"234":3,"235":3,"236":3,"238":3,"240":7,"241":7,"243":7,"244":7,"245":7,"246":7,"247":7,"248":7,"249":7,"250":7,"251":7,"252":7,"253":7,"254":7,"255":7,"256":7,"257":7,"258":7,"260":7,"263":28,"264":49,"266":49,"267":49,"268":49,"269":49,"270":49,"271":49,"272":49,"273":49,"274":49,"275":49,"276":49,"277":49,"278":49,"279":49,"280":49,"281":49,"283":49},"branchMap":{"0":{"type":"branch","line":57,"loc":{"start":{"line":57,"column":0},"end":{"line":62,"column":1}},"locations":[{"start":{"line":57,"column":0},"end":{"line":62,"column":1}}]},"1":{"type":"branch","line":64,"loc":{"start":{"line":64,"column":0},"end":{"line":73,"column":1}},"locations":[{"start":{"line":64,"column":0},"end":{"line":73,"column":1}}]},"2":{"type":"branch","line":69,"loc":{"start":{"line":69,"column":44},"end":{"line":69,"column":56}},"locations":[{"start":{"line":69,"column":44},"end":{"line":69,"column":56}}]},"3":{"type":"branch","line":75,"loc":{"start":{"line":75,"column":0},"end":{"line":78,"column":1}},"locations":[{"start":{"line":75,"column":0},"end":{"line":78,"column":1}}]},"4":{"type":"branch","line":76,"loc":{"start":{"line":76,"column":13},"end":{"line":76,"column":24}},"locations":[{"start":{"line":76,"column":13},"end":{"line":76,"column":24}}]},"5":{"type":"branch","line":76,"loc":{"start":{"line":76,"column":20},"end":{"line":78,"column":1}},"locations":[{"start":{"line":76,"column":20},"end":{"line":78,"column":1}}]},"6":{"type":"branch","line":98,"loc":{"start":{"line":98,"column":0},"end":{"line":203,"column":1}},"locations":[{"start":{"line":98,"column":0},"end":{"line":203,"column":1}}]},"7":{"type":"branch","line":122,"loc":{"start":{"line":122,"column":7},"end":{"line":125,"column":14}},"locations":[{"start":{"line":122,"column":7},"end":{"line":125,"column":14}}]},"8":{"type":"branch","line":133,"loc":{"start":{"line":133,"column":7},"end":{"line":136,"column":14}},"locations":[{"start":{"line":133,"column":7},"end":{"line":136,"column":14}}]},"9":{"type":"branch","line":163,"loc":{"start":{"line":163,"column":9},"end":{"line":170,"column":19}},"locations":[{"start":{"line":163,"column":9},"end":{"line":170,"column":19}}]},"10":{"type":"branch","line":172,"loc":{"start":{"line":172,"column":9},"end":{"line":179,"column":19}},"locations":[{"start":{"line":172,"column":9},"end":{"line":179,"column":19}}]},"11":{"type":"branch","line":185,"loc":{"start":{"line":185,"column":15},"end":{"line":196,"column":3}},"locations":[{"start":{"line":185,"column":15},"end":{"line":196,"column":3}}]},"12":{"type":"branch","line":196,"loc":{"start":{"line":196,"column":2},"end":{"line":203,"column":1}},"locations":[{"start":{"line":196,"column":2},"end":{"line":203,"column":1}}]},"13":{"type":"branch","line":115,"loc":{"start":{"line":115,"column":26},"end":{"line":118,"column":4}},"locations":[{"start":{"line":115,"column":26},"end":{"line":118,"column":4}}]},"14":{"type":"branch","line":116,"loc":{"start":{"line":116,"column":53},"end":{"line":116,"column":60}},"locations":[{"start":{"line":116,"column":53},"end":{"line":116,"column":60}}]},"15":{"type":"branch","line":167,"loc":{"start":{"line":167,"column":21},"end":{"line":167,"column":70}},"locations":[{"start":{"line":167,"column":21},"end":{"line":167,"column":70}}]},"16":{"type":"branch","line":176,"loc":{"start":{"line":176,"column":21},"end":{"line":176,"column":63}},"locations":[{"start":{"line":176,"column":21},"end":{"line":176,"column":63}}]},"17":{"type":"branch","line":217,"loc":{"start":{"line":217,"column":7},"end":{"line":284,"column":1}},"locations":[{"start":{"line":217,"column":7},"end":{"line":284,"column":1}}]},"18":{"type":"branch","line":218,"loc":{"start":{"line":218,"column":40},"end":{"line":218,"column":83}},"locations":[{"start":{"line":218,"column":40},"end":{"line":218,"column":83}}]},"19":{"type":"branch","line":228,"loc":{"start":{"line":228,"column":19},"end":{"line":261,"column":3}},"locations":[{"start":{"line":228,"column":19},"end":{"line":261,"column":3}}]},"20":{"type":"branch","line":229,"loc":{"start":{"line":229,"column":19},"end":{"line":229,"column":48}},"locations":[{"start":{"line":229,"column":19},"end":{"line":229,"column":48}}]},"21":{"type":"branch","line":229,"loc":{"start":{"line":229,"column":45},"end":{"line":231,"column":17}},"locations":[{"start":{"line":229,"column":45},"end":{"line":231,"column":17}}]},"22":{"type":"branch","line":231,"loc":{"start":{"line":231,"column":8},"end":{"line":231,"column":24}},"locations":[{"start":{"line":231,"column":8},"end":{"line":231,"column":24}}]},"23":{"type":"branch","line":231,"loc":{"start":{"line":231,"column":24},"end":{"line":239,"column":5}},"locations":[{"start":{"line":231,"column":24},"end":{"line":239,"column":5}}]},"24":{"type":"branch","line":235,"loc":{"start":{"line":235,"column":30},"end":{"line":235,"column":54}},"locations":[{"start":{"line":235,"column":30},"end":{"line":235,"column":54}}]},"25":{"type":"branch","line":235,"loc":{"start":{"line":235,"column":44},"end":{"line":235,"column":78}},"locations":[{"start":{"line":235,"column":44},"end":{"line":235,"column":78}}]},"26":{"type":"branch","line":239,"loc":{"start":{"line":239,"column":4},"end":{"line":261,"column":3}},"locations":[{"start":{"line":239,"column":4},"end":{"line":261,"column":3}}]},"27":{"type":"branch","line":261,"loc":{"start":{"line":261,"column":2},"end":{"line":265,"column":48}},"locations":[{"start":{"line":261,"column":2},"end":{"line":265,"column":48}}]},"28":{"type":"branch","line":265,"loc":{"start":{"line":265,"column":26},"end":{"line":265,"column":51}},"locations":[{"start":{"line":265,"column":26},"end":{"line":265,"column":51}}]},"29":{"type":"branch","line":265,"loc":{"start":{"line":265,"column":73},"end":{"line":265,"column":90}},"locations":[{"start":{"line":265,"column":73},"end":{"line":265,"column":90}}]},"30":{"type":"branch","line":273,"loc":{"start":{"line":273,"column":16},"end":{"line":273,"column":35}},"locations":[{"start":{"line":273,"column":16},"end":{"line":273,"column":35}}]},"31":{"type":"branch","line":274,"loc":{"start":{"line":274,"column":22},"end":{"line":274,"column":40}},"locations":[{"start":{"line":274,"column":22},"end":{"line":274,"column":40}}]},"32":{"type":"branch","line":223,"loc":{"start":{"line":223,"column":13},"end":{"line":223,"column":77}},"locations":[{"start":{"line":223,"column":13},"end":{"line":223,"column":77}}]}},"b":{"0":[7],"1":[7],"2":[0],"3":[35],"4":[27],"5":[8],"6":[35],"7":[8],"8":[8],"9":[4],"10":[5],"11":[8],"12":[27],"13":[1],"14":[0],"15":[2],"16":[3],"17":[49],"18":[22],"19":[21],"20":[11],"21":[10],"22":[8],"23":[3],"24":[1],"25":[2],"26":[7],"27":[28],"28":[0],"29":[27],"30":[25],"31":[27],"32":[11]},"fnMap":{"0":{"name":"formatXlm","decl":{"start":{"line":57,"column":0},"end":{"line":62,"column":1}},"loc":{"start":{"line":57,"column":0},"end":{"line":62,"column":1}},"line":57},"1":{"name":"computeNextPayout","decl":{"start":{"line":64,"column":0},"end":{"line":73,"column":1}},"loc":{"start":{"line":64,"column":0},"end":{"line":73,"column":1}},"line":64},"2":{"name":"formatDate","decl":{"start":{"line":75,"column":0},"end":{"line":78,"column":1}},"loc":{"start":{"line":75,"column":0},"end":{"line":78,"column":1}},"line":75},"3":{"name":"GroupCardUI","decl":{"start":{"line":98,"column":0},"end":{"line":203,"column":1}},"loc":{"start":{"line":98,"column":0},"end":{"line":203,"column":1}},"line":98},"4":{"name":"handleCardClick","decl":{"start":{"line":115,"column":26},"end":{"line":118,"column":4}},"loc":{"start":{"line":115,"column":26},"end":{"line":118,"column":4}},"line":115},"5":{"name":"onClick","decl":{"start":{"line":167,"column":21},"end":{"line":167,"column":70}},"loc":{"start":{"line":167,"column":21},"end":{"line":167,"column":70}},"line":167},"6":{"name":"onClick","decl":{"start":{"line":176,"column":21},"end":{"line":176,"column":63}},"loc":{"start":{"line":176,"column":21},"end":{"line":176,"column":63}},"line":176},"7":{"name":"GroupCard","decl":{"start":{"line":217,"column":7},"end":{"line":284,"column":1}},"loc":{"start":{"line":217,"column":7},"end":{"line":284,"column":1}},"line":217},"8":{"name":"queryFn","decl":{"start":{"line":223,"column":13},"end":{"line":223,"column":77}},"loc":{"start":{"line":223,"column":13},"end":{"line":223,"column":77}},"line":223}},"f":{"0":7,"1":7,"2":35,"3":35,"4":1,"5":2,"6":3,"7":49,"8":11}} +,"/home/kimani/drips/Stellar-Save/frontend/src/components/TransactionHistory.tsx": {"path":"/home/kimani/drips/Stellar-Save/frontend/src/components/TransactionHistory.tsx","all":false,"statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":1,"column":3}},"32":{"start":{"line":33,"column":0},"end":{"line":33,"column":46}},"33":{"start":{"line":34,"column":0},"end":{"line":34,"column":49}},"34":{"start":{"line":35,"column":0},"end":{"line":35,"column":41}},"35":{"start":{"line":36,"column":0},"end":{"line":36,"column":53}},"36":{"start":{"line":37,"column":0},"end":{"line":37,"column":2}},"51":{"start":{"line":52,"column":0},"end":{"line":52,"column":40}},"52":{"start":{"line":53,"column":0},"end":{"line":53,"column":18}},"53":{"start":{"line":54,"column":0},"end":{"line":54,"column":18}},"54":{"start":{"line":55,"column":0},"end":{"line":55,"column":22}},"55":{"start":{"line":56,"column":0},"end":{"line":56,"column":27}},"56":{"start":{"line":57,"column":0},"end":{"line":57,"column":61}},"57":{"start":{"line":58,"column":0},"end":{"line":58,"column":73}},"59":{"start":{"line":60,"column":0},"end":{"line":60,"column":31}},"60":{"start":{"line":61,"column":0},"end":{"line":61,"column":63}},"62":{"start":{"line":63,"column":0},"end":{"line":63,"column":82}},"63":{"start":{"line":64,"column":0},"end":{"line":64,"column":48}},"65":{"start":{"line":66,"column":0},"end":{"line":66,"column":16}},"66":{"start":{"line":67,"column":0},"end":{"line":67,"column":86}},"67":{"start":{"line":68,"column":0},"end":{"line":68,"column":31}},"68":{"start":{"line":69,"column":0},"end":{"line":69,"column":15}},"69":{"start":{"line":70,"column":0},"end":{"line":70,"column":31}},"70":{"start":{"line":71,"column":0},"end":{"line":71,"column":30}},"71":{"start":{"line":72,"column":0},"end":{"line":72,"column":78}},"72":{"start":{"line":73,"column":0},"end":{"line":73,"column":46}},"73":{"start":{"line":74,"column":0},"end":{"line":74,"column":81}},"74":{"start":{"line":75,"column":0},"end":{"line":75,"column":30}},"75":{"start":{"line":76,"column":0},"end":{"line":76,"column":15}},"76":{"start":{"line":77,"column":0},"end":{"line":77,"column":19}},"77":{"start":{"line":78,"column":0},"end":{"line":78,"column":24}},"78":{"start":{"line":79,"column":0},"end":{"line":79,"column":21}},"79":{"start":{"line":80,"column":0},"end":{"line":80,"column":8}},"80":{"start":{"line":81,"column":0},"end":{"line":81,"column":1}},"84":{"start":{"line":85,"column":0},"end":{"line":85,"column":42}},"85":{"start":{"line":86,"column":0},"end":{"line":86,"column":245}},"86":{"start":{"line":87,"column":0},"end":{"line":87,"column":234}},"87":{"start":{"line":88,"column":0},"end":{"line":88,"column":245}},"88":{"start":{"line":89,"column":0},"end":{"line":89,"column":223}},"89":{"start":{"line":90,"column":0},"end":{"line":90,"column":221}},"90":{"start":{"line":91,"column":0},"end":{"line":91,"column":2}},"94":{"start":{"line":95,"column":0},"end":{"line":95,"column":94}},"95":{"start":{"line":96,"column":0},"end":{"line":96,"column":21}},"96":{"start":{"line":97,"column":0},"end":{"line":97,"column":21}},"97":{"start":{"line":98,"column":0},"end":{"line":98,"column":22}},"98":{"start":{"line":99,"column":0},"end":{"line":99,"column":18}},"99":{"start":{"line":100,"column":0},"end":{"line":100,"column":23}},"100":{"start":{"line":101,"column":0},"end":{"line":101,"column":19}},"101":{"start":{"line":102,"column":0},"end":{"line":102,"column":2}},"103":{"start":{"line":104,"column":0},"end":{"line":104,"column":54}},"104":{"start":{"line":105,"column":0},"end":{"line":105,"column":44}},"105":{"start":{"line":106,"column":0},"end":{"line":106,"column":49}},"106":{"start":{"line":107,"column":0},"end":{"line":107,"column":51}},"108":{"start":{"line":109,"column":0},"end":{"line":109,"column":10}},"109":{"start":{"line":110,"column":0},"end":{"line":110,"column":5}},"110":{"start":{"line":111,"column":0},"end":{"line":111,"column":25}},"111":{"start":{"line":112,"column":0},"end":{"line":112,"column":25}},"112":{"start":{"line":113,"column":0},"end":{"line":113,"column":17}},"113":{"start":{"line":114,"column":0},"end":{"line":114,"column":40}},"114":{"start":{"line":115,"column":0},"end":{"line":115,"column":95}},"115":{"start":{"line":116,"column":0},"end":{"line":116,"column":21}},"116":{"start":{"line":117,"column":0},"end":{"line":117,"column":6}},"117":{"start":{"line":118,"column":0},"end":{"line":118,"column":5}},"118":{"start":{"line":119,"column":0},"end":{"line":119,"column":20}},"119":{"start":{"line":120,"column":0},"end":{"line":120,"column":25}},"120":{"start":{"line":121,"column":0},"end":{"line":121,"column":17}},"121":{"start":{"line":122,"column":0},"end":{"line":122,"column":21}},"122":{"start":{"line":123,"column":0},"end":{"line":123,"column":74}},"123":{"start":{"line":124,"column":0},"end":{"line":124,"column":13}},"124":{"start":{"line":125,"column":0},"end":{"line":125,"column":36}},"125":{"start":{"line":126,"column":0},"end":{"line":126,"column":22}},"126":{"start":{"line":127,"column":0},"end":{"line":127,"column":62}},"127":{"start":{"line":128,"column":0},"end":{"line":128,"column":63}},"128":{"start":{"line":129,"column":0},"end":{"line":129,"column":10}},"130":{"start":{"line":131,"column":0},"end":{"line":131,"column":6}},"131":{"start":{"line":132,"column":0},"end":{"line":132,"column":5}},"132":{"start":{"line":133,"column":0},"end":{"line":133,"column":22}},"133":{"start":{"line":134,"column":0},"end":{"line":134,"column":27}},"134":{"start":{"line":135,"column":0},"end":{"line":135,"column":17}},"135":{"start":{"line":136,"column":0},"end":{"line":136,"column":21}},"136":{"start":{"line":137,"column":0},"end":{"line":137,"column":78}},"137":{"start":{"line":138,"column":0},"end":{"line":138,"column":74}},"138":{"start":{"line":139,"column":0},"end":{"line":139,"column":52}},"139":{"start":{"line":140,"column":0},"end":{"line":140,"column":16}},"140":{"start":{"line":141,"column":0},"end":{"line":141,"column":21}},"141":{"start":{"line":142,"column":0},"end":{"line":142,"column":27}},"142":{"start":{"line":143,"column":0},"end":{"line":143,"column":28}},"143":{"start":{"line":144,"column":0},"end":{"line":144,"column":60}},"145":{"start":{"line":146,"column":0},"end":{"line":146,"column":49}},"146":{"start":{"line":147,"column":0},"end":{"line":147,"column":23}},"148":{"start":{"line":149,"column":0},"end":{"line":149,"column":8}},"149":{"start":{"line":150,"column":0},"end":{"line":150,"column":6}},"150":{"start":{"line":151,"column":0},"end":{"line":151,"column":5}},"151":{"start":{"line":152,"column":0},"end":{"line":152,"column":25}},"152":{"start":{"line":153,"column":0},"end":{"line":153,"column":26}},"153":{"start":{"line":154,"column":0},"end":{"line":154,"column":16}},"154":{"start":{"line":155,"column":0},"end":{"line":155,"column":21}},"155":{"start":{"line":156,"column":0},"end":{"line":156,"column":6}},"156":{"start":{"line":157,"column":0},"end":{"line":157,"column":5}},"157":{"start":{"line":158,"column":0},"end":{"line":158,"column":20}},"158":{"start":{"line":159,"column":0},"end":{"line":159,"column":25}},"159":{"start":{"line":160,"column":0},"end":{"line":160,"column":17}},"160":{"start":{"line":161,"column":0},"end":{"line":161,"column":22}},"161":{"start":{"line":162,"column":0},"end":{"line":162,"column":74}},"162":{"start":{"line":163,"column":0},"end":{"line":163,"column":71}},"163":{"start":{"line":164,"column":0},"end":{"line":164,"column":24}},"164":{"start":{"line":165,"column":0},"end":{"line":165,"column":21}},"166":{"start":{"line":167,"column":0},"end":{"line":167,"column":6}},"167":{"start":{"line":168,"column":0},"end":{"line":168,"column":5}},"168":{"start":{"line":169,"column":0},"end":{"line":169,"column":20}},"169":{"start":{"line":170,"column":0},"end":{"line":170,"column":25}},"170":{"start":{"line":171,"column":0},"end":{"line":171,"column":14}},"171":{"start":{"line":172,"column":0},"end":{"line":172,"column":20}},"172":{"start":{"line":173,"column":0},"end":{"line":173,"column":22}},"173":{"start":{"line":174,"column":0},"end":{"line":174,"column":74}},"174":{"start":{"line":175,"column":0},"end":{"line":175,"column":68}},"175":{"start":{"line":176,"column":0},"end":{"line":176,"column":31}},"176":{"start":{"line":177,"column":0},"end":{"line":177,"column":21}},"178":{"start":{"line":179,"column":0},"end":{"line":179,"column":6}},"179":{"start":{"line":180,"column":0},"end":{"line":180,"column":5}},"180":{"start":{"line":181,"column":0},"end":{"line":181,"column":22}},"181":{"start":{"line":182,"column":0},"end":{"line":182,"column":27}},"182":{"start":{"line":183,"column":0},"end":{"line":183,"column":17}},"183":{"start":{"line":184,"column":0},"end":{"line":184,"column":21}},"184":{"start":{"line":185,"column":0},"end":{"line":185,"column":74}},"185":{"start":{"line":186,"column":0},"end":{"line":186,"column":13}},"186":{"start":{"line":187,"column":0},"end":{"line":187,"column":30}},"187":{"start":{"line":188,"column":0},"end":{"line":188,"column":22}},"188":{"start":{"line":189,"column":0},"end":{"line":189,"column":107}},"189":{"start":{"line":190,"column":0},"end":{"line":190,"column":10}},"191":{"start":{"line":192,"column":0},"end":{"line":192,"column":6}},"192":{"start":{"line":193,"column":0},"end":{"line":193,"column":5}},"193":{"start":{"line":194,"column":0},"end":{"line":194,"column":20}},"194":{"start":{"line":195,"column":0},"end":{"line":195,"column":23}},"195":{"start":{"line":196,"column":0},"end":{"line":196,"column":16}},"196":{"start":{"line":197,"column":0},"end":{"line":197,"column":22}},"197":{"start":{"line":198,"column":0},"end":{"line":198,"column":74}},"198":{"start":{"line":199,"column":0},"end":{"line":199,"column":13}},"199":{"start":{"line":200,"column":0},"end":{"line":200,"column":50}},"200":{"start":{"line":201,"column":0},"end":{"line":201,"column":25}},"201":{"start":{"line":202,"column":0},"end":{"line":202,"column":35}},"202":{"start":{"line":203,"column":0},"end":{"line":203,"column":27}},"203":{"start":{"line":204,"column":0},"end":{"line":204,"column":46}},"204":{"start":{"line":205,"column":0},"end":{"line":205,"column":9}},"206":{"start":{"line":207,"column":0},"end":{"line":207,"column":15}},"208":{"start":{"line":209,"column":0},"end":{"line":209,"column":6}},"209":{"start":{"line":210,"column":0},"end":{"line":210,"column":4}},"210":{"start":{"line":211,"column":0},"end":{"line":211,"column":1}},"232":{"start":{"line":233,"column":0},"end":{"line":233,"column":36}},"233":{"start":{"line":234,"column":0},"end":{"line":234,"column":23}},"234":{"start":{"line":235,"column":0},"end":{"line":235,"column":13}},"235":{"start":{"line":236,"column":0},"end":{"line":236,"column":33}},"236":{"start":{"line":237,"column":0},"end":{"line":237,"column":29}},"237":{"start":{"line":238,"column":0},"end":{"line":238,"column":49}},"238":{"start":{"line":239,"column":0},"end":{"line":239,"column":47}},"239":{"start":{"line":240,"column":0},"end":{"line":240,"column":35}},"241":{"start":{"line":242,"column":0},"end":{"line":242,"column":70}},"242":{"start":{"line":243,"column":0},"end":{"line":243,"column":48}},"243":{"start":{"line":244,"column":0},"end":{"line":244,"column":58}},"244":{"start":{"line":245,"column":0},"end":{"line":245,"column":62}},"245":{"start":{"line":246,"column":0},"end":{"line":246,"column":100}},"246":{"start":{"line":247,"column":0},"end":{"line":247,"column":97}},"249":{"start":{"line":250,"column":0},"end":{"line":250,"column":19}},"250":{"start":{"line":251,"column":0},"end":{"line":251,"column":19}},"251":{"start":{"line":252,"column":0},"end":{"line":252,"column":41}},"252":{"start":{"line":253,"column":0},"end":{"line":253,"column":13}},"253":{"start":{"line":254,"column":0},"end":{"line":254,"column":5}},"254":{"start":{"line":255,"column":0},"end":{"line":255,"column":21}},"255":{"start":{"line":256,"column":0},"end":{"line":256,"column":19}},"256":{"start":{"line":257,"column":0},"end":{"line":257,"column":54}},"257":{"start":{"line":258,"column":0},"end":{"line":258,"column":22}},"258":{"start":{"line":259,"column":0},"end":{"line":259,"column":66}},"259":{"start":{"line":260,"column":0},"end":{"line":260,"column":8}},"260":{"start":{"line":261,"column":0},"end":{"line":261,"column":20}},"262":{"start":{"line":263,"column":0},"end":{"line":263,"column":43}},"263":{"start":{"line":264,"column":0},"end":{"line":264,"column":59}},"264":{"start":{"line":265,"column":0},"end":{"line":265,"column":8}},"265":{"start":{"line":266,"column":0},"end":{"line":266,"column":40}},"266":{"start":{"line":267,"column":0},"end":{"line":267,"column":33}},"269":{"start":{"line":270,"column":0},"end":{"line":270,"column":34}},"270":{"start":{"line":271,"column":0},"end":{"line":271,"column":50}},"271":{"start":{"line":272,"column":0},"end":{"line":272,"column":63}},"272":{"start":{"line":273,"column":0},"end":{"line":273,"column":33}},"274":{"start":{"line":275,"column":0},"end":{"line":275,"column":58}},"276":{"start":{"line":277,"column":0},"end":{"line":277,"column":30}},"277":{"start":{"line":278,"column":0},"end":{"line":278,"column":63}},"278":{"start":{"line":279,"column":0},"end":{"line":279,"column":19}},"279":{"start":{"line":280,"column":0},"end":{"line":280,"column":4}},"281":{"start":{"line":282,"column":0},"end":{"line":282,"column":10}},"282":{"start":{"line":283,"column":0},"end":{"line":283,"column":9}},"284":{"start":{"line":285,"column":0},"end":{"line":285,"column":110}},"285":{"start":{"line":286,"column":0},"end":{"line":286,"column":50}},"287":{"start":{"line":288,"column":0},"end":{"line":288,"column":21}},"288":{"start":{"line":289,"column":0},"end":{"line":289,"column":18}},"289":{"start":{"line":290,"column":0},"end":{"line":290,"column":16}},"290":{"start":{"line":291,"column":0},"end":{"line":291,"column":22}},"291":{"start":{"line":292,"column":0},"end":{"line":292,"column":22}},"292":{"start":{"line":293,"column":0},"end":{"line":293,"column":28}},"293":{"start":{"line":294,"column":0},"end":{"line":294,"column":57}},"294":{"start":{"line":295,"column":0},"end":{"line":295,"column":32}},"296":{"start":{"line":297,"column":0},"end":{"line":297,"column":52}},"297":{"start":{"line":298,"column":0},"end":{"line":298,"column":35}},"298":{"start":{"line":299,"column":0},"end":{"line":299,"column":91}},"299":{"start":{"line":300,"column":0},"end":{"line":300,"column":13}},"300":{"start":{"line":301,"column":0},"end":{"line":301,"column":20}},"301":{"start":{"line":302,"column":0},"end":{"line":302,"column":22}},"302":{"start":{"line":303,"column":0},"end":{"line":303,"column":63}},"304":{"start":{"line":305,"column":0},"end":{"line":305,"column":23}},"306":{"start":{"line":307,"column":0},"end":{"line":307,"column":14}},"308":{"start":{"line":309,"column":0},"end":{"line":309,"column":73}},"311":{"start":{"line":312,"column":0},"end":{"line":312,"column":47}},"312":{"start":{"line":313,"column":0},"end":{"line":313,"column":17}},"313":{"start":{"line":314,"column":0},"end":{"line":314,"column":25}},"314":{"start":{"line":315,"column":0},"end":{"line":315,"column":27}},"315":{"start":{"line":316,"column":0},"end":{"line":316,"column":27}},"316":{"start":{"line":317,"column":0},"end":{"line":317,"column":31}},"317":{"start":{"line":318,"column":0},"end":{"line":318,"column":42}},"318":{"start":{"line":319,"column":0},"end":{"line":319,"column":43}},"319":{"start":{"line":320,"column":0},"end":{"line":320,"column":54}},"320":{"start":{"line":321,"column":0},"end":{"line":321,"column":43}},"321":{"start":{"line":322,"column":0},"end":{"line":322,"column":36}},"322":{"start":{"line":323,"column":0},"end":{"line":323,"column":27}},"323":{"start":{"line":324,"column":0},"end":{"line":324,"column":18}},"324":{"start":{"line":325,"column":0},"end":{"line":325,"column":35}},"325":{"start":{"line":326,"column":0},"end":{"line":326,"column":108}},"326":{"start":{"line":327,"column":0},"end":{"line":327,"column":46}},"327":{"start":{"line":328,"column":0},"end":{"line":328,"column":20}},"329":{"start":{"line":330,"column":0},"end":{"line":330,"column":34}},"330":{"start":{"line":331,"column":0},"end":{"line":331,"column":108}},"331":{"start":{"line":332,"column":0},"end":{"line":332,"column":85}},"332":{"start":{"line":333,"column":0},"end":{"line":333,"column":20}},"334":{"start":{"line":335,"column":0},"end":{"line":335,"column":12}},"335":{"start":{"line":336,"column":0},"end":{"line":336,"column":15}},"336":{"start":{"line":337,"column":0},"end":{"line":337,"column":32}},"337":{"start":{"line":338,"column":0},"end":{"line":338,"column":35}},"338":{"start":{"line":339,"column":0},"end":{"line":339,"column":28}},"339":{"start":{"line":340,"column":0},"end":{"line":340,"column":72}},"340":{"start":{"line":341,"column":0},"end":{"line":341,"column":68}},"341":{"start":{"line":342,"column":0},"end":{"line":342,"column":12}},"342":{"start":{"line":343,"column":0},"end":{"line":343,"column":10}},"343":{"start":{"line":344,"column":0},"end":{"line":344,"column":12}},"344":{"start":{"line":345,"column":0},"end":{"line":345,"column":10}},"346":{"start":{"line":347,"column":0},"end":{"line":347,"column":1}},"348":{"start":{"line":349,"column":0},"end":{"line":349,"column":34}}},"s":{"0":1,"32":1,"33":1,"34":1,"35":1,"36":1,"51":10,"52":10,"53":10,"54":10,"55":10,"56":10,"57":10,"59":10,"60":10,"62":6,"63":10,"65":10,"66":10,"67":10,"68":10,"69":10,"70":10,"71":10,"72":10,"73":10,"74":10,"75":10,"76":10,"77":10,"78":10,"79":10,"80":10,"84":1,"85":1,"86":1,"87":1,"88":1,"89":1,"90":1,"94":1,"95":1,"96":1,"97":1,"98":1,"99":1,"100":1,"101":1,"103":16,"104":16,"105":1,"106":15,"108":16,"109":16,"110":16,"111":16,"112":16,"113":16,"114":47,"115":16,"116":16,"117":16,"118":16,"119":16,"120":16,"121":16,"122":16,"123":47,"124":47,"125":47,"126":47,"127":47,"128":47,"130":16,"131":16,"132":16,"133":16,"134":16,"135":16,"136":16,"137":16,"138":47,"139":47,"140":47,"141":47,"142":47,"143":47,"145":47,"146":47,"148":47,"149":16,"150":16,"151":16,"152":16,"153":16,"154":16,"155":16,"156":16,"157":16,"158":16,"159":16,"160":16,"161":16,"162":47,"163":47,"164":47,"166":16,"167":16,"168":16,"169":16,"170":16,"171":16,"172":16,"173":16,"174":47,"175":47,"176":47,"178":16,"179":16,"180":16,"181":16,"182":16,"183":16,"184":16,"185":47,"186":47,"187":47,"188":47,"189":47,"191":16,"192":16,"193":16,"194":16,"195":16,"196":16,"197":16,"198":47,"199":47,"200":47,"201":47,"202":47,"203":47,"204":47,"206":47,"208":16,"209":16,"210":16,"232":1,"233":41,"234":41,"235":41,"236":41,"237":41,"238":41,"239":41,"241":41,"242":41,"243":41,"244":41,"245":41,"246":41,"249":41,"250":16,"251":6,"252":6,"253":6,"254":10,"255":10,"256":10,"257":10,"258":6,"259":10,"260":10,"262":4,"263":4,"264":10,"265":10,"266":41,"269":41,"270":31,"271":0,"272":41,"274":41,"276":41,"277":41,"278":41,"279":41,"281":41,"282":41,"284":41,"285":41,"287":41,"288":41,"289":41,"290":41,"291":41,"292":41,"293":41,"294":41,"296":41,"297":41,"298":45,"299":41,"300":41,"301":41,"302":12,"304":12,"306":41,"308":41,"311":41,"312":41,"313":41,"314":41,"315":41,"316":41,"317":41,"318":41,"319":41,"320":41,"321":41,"322":41,"323":41,"324":41,"325":19,"326":19,"327":19,"329":41,"330":32,"331":32,"332":32,"334":41,"335":41,"336":41,"337":41,"338":41,"339":41,"340":41,"341":41,"342":41,"343":41,"344":41,"346":41,"348":1},"branchMap":{"0":{"type":"branch","line":52,"loc":{"start":{"line":52,"column":0},"end":{"line":81,"column":1}},"locations":[{"start":{"line":52,"column":0},"end":{"line":81,"column":1}}]},"1":{"type":"branch","line":57,"loc":{"start":{"line":57,"column":35},"end":{"line":57,"column":61}},"locations":[{"start":{"line":57,"column":35},"end":{"line":57,"column":61}}]},"2":{"type":"branch","line":61,"loc":{"start":{"line":61,"column":15},"end":{"line":61,"column":63}},"locations":[{"start":{"line":61,"column":15},"end":{"line":61,"column":63}}]},"3":{"type":"branch","line":61,"loc":{"start":{"line":61,"column":61},"end":{"line":64,"column":45}},"locations":[{"start":{"line":61,"column":61},"end":{"line":64,"column":45}}]},"4":{"type":"branch","line":64,"loc":{"start":{"line":64,"column":34},"end":{"line":64,"column":48}},"locations":[{"start":{"line":64,"column":34},"end":{"line":64,"column":48}}]},"5":{"type":"branch","line":67,"loc":{"start":{"line":67,"column":12},"end":{"line":67,"column":85}},"locations":[{"start":{"line":67,"column":12},"end":{"line":67,"column":85}}]},"6":{"type":"branch","line":67,"loc":{"start":{"line":67,"column":20},"end":{"line":67,"column":85}},"locations":[{"start":{"line":67,"column":20},"end":{"line":67,"column":85}}]},"7":{"type":"branch","line":68,"loc":{"start":{"line":68,"column":9},"end":{"line":80,"column":6}},"locations":[{"start":{"line":68,"column":9},"end":{"line":80,"column":6}}]},"8":{"type":"branch","line":72,"loc":{"start":{"line":72,"column":36},"end":{"line":72,"column":78}},"locations":[{"start":{"line":72,"column":36},"end":{"line":72,"column":78}}]},"9":{"type":"branch","line":73,"loc":{"start":{"line":73,"column":37},"end":{"line":73,"column":46}},"locations":[{"start":{"line":73,"column":37},"end":{"line":73,"column":46}}]},"10":{"type":"branch","line":74,"loc":{"start":{"line":74,"column":19},"end":{"line":74,"column":81}},"locations":[{"start":{"line":74,"column":19},"end":{"line":74,"column":81}}]},"11":{"type":"branch","line":75,"loc":{"start":{"line":75,"column":14},"end":{"line":75,"column":30}},"locations":[{"start":{"line":75,"column":14},"end":{"line":75,"column":30}}]},"12":{"type":"branch","line":104,"loc":{"start":{"line":104,"column":0},"end":{"line":211,"column":1}},"locations":[{"start":{"line":104,"column":0},"end":{"line":211,"column":1}}]},"13":{"type":"branch","line":105,"loc":{"start":{"line":105,"column":35},"end":{"line":106,"column":49}},"locations":[{"start":{"line":105,"column":35},"end":{"line":106,"column":49}}]},"14":{"type":"branch","line":106,"loc":{"start":{"line":106,"column":6},"end":{"line":107,"column":51}},"locations":[{"start":{"line":106,"column":6},"end":{"line":107,"column":51}}]},"15":{"type":"branch","line":114,"loc":{"start":{"line":114,"column":22},"end":{"line":115,"column":95}},"locations":[{"start":{"line":114,"column":22},"end":{"line":115,"column":95}}]},"16":{"type":"branch","line":123,"loc":{"start":{"line":123,"column":18},"end":{"line":129,"column":10}},"locations":[{"start":{"line":123,"column":18},"end":{"line":129,"column":10}}]},"17":{"type":"branch","line":125,"loc":{"start":{"line":125,"column":24},"end":{"line":125,"column":36}},"locations":[{"start":{"line":125,"column":24},"end":{"line":125,"column":36}}]},"18":{"type":"branch","line":127,"loc":{"start":{"line":127,"column":36},"end":{"line":127,"column":47}},"locations":[{"start":{"line":127,"column":36},"end":{"line":127,"column":47}}]},"19":{"type":"branch","line":127,"loc":{"start":{"line":127,"column":47},"end":{"line":127,"column":62}},"locations":[{"start":{"line":127,"column":47},"end":{"line":127,"column":62}}]},"20":{"type":"branch","line":138,"loc":{"start":{"line":138,"column":18},"end":{"line":149,"column":8}},"locations":[{"start":{"line":138,"column":18},"end":{"line":149,"column":8}}]},"21":{"type":"branch","line":139,"loc":{"start":{"line":139,"column":38},"end":{"line":139,"column":50}},"locations":[{"start":{"line":139,"column":38},"end":{"line":139,"column":50}}]},"22":{"type":"branch","line":144,"loc":{"start":{"line":144,"column":26},"end":{"line":144,"column":47}},"locations":[{"start":{"line":144,"column":26},"end":{"line":144,"column":47}}]},"23":{"type":"branch","line":144,"loc":{"start":{"line":144,"column":30},"end":{"line":144,"column":60}},"locations":[{"start":{"line":144,"column":30},"end":{"line":144,"column":60}}]},"24":{"type":"branch","line":162,"loc":{"start":{"line":162,"column":18},"end":{"line":165,"column":21}},"locations":[{"start":{"line":162,"column":18},"end":{"line":165,"column":21}}]},"25":{"type":"branch","line":174,"loc":{"start":{"line":174,"column":18},"end":{"line":177,"column":21}},"locations":[{"start":{"line":174,"column":18},"end":{"line":177,"column":21}}]},"26":{"type":"branch","line":176,"loc":{"start":{"line":176,"column":18},"end":{"line":176,"column":31}},"locations":[{"start":{"line":176,"column":18},"end":{"line":176,"column":31}}]},"27":{"type":"branch","line":185,"loc":{"start":{"line":185,"column":18},"end":{"line":190,"column":10}},"locations":[{"start":{"line":185,"column":18},"end":{"line":190,"column":10}}]},"28":{"type":"branch","line":189,"loc":{"start":{"line":189,"column":34},"end":{"line":189,"column":58}},"locations":[{"start":{"line":189,"column":34},"end":{"line":189,"column":58}}]},"29":{"type":"branch","line":189,"loc":{"start":{"line":189,"column":46},"end":{"line":189,"column":107}},"locations":[{"start":{"line":189,"column":46},"end":{"line":189,"column":107}}]},"30":{"type":"branch","line":189,"loc":{"start":{"line":189,"column":87},"end":{"line":189,"column":107}},"locations":[{"start":{"line":189,"column":87},"end":{"line":189,"column":107}}]},"31":{"type":"branch","line":198,"loc":{"start":{"line":198,"column":18},"end":{"line":207,"column":15}},"locations":[{"start":{"line":198,"column":18},"end":{"line":207,"column":15}}]},"32":{"type":"branch","line":233,"loc":{"start":{"line":233,"column":7},"end":{"line":347,"column":1}},"locations":[{"start":{"line":233,"column":7},"end":{"line":347,"column":1}}]},"33":{"type":"branch","line":239,"loc":{"start":{"line":239,"column":18},"end":{"line":239,"column":47}},"locations":[{"start":{"line":239,"column":18},"end":{"line":239,"column":47}}]},"34":{"type":"branch","line":240,"loc":{"start":{"line":240,"column":14},"end":{"line":240,"column":35}},"locations":[{"start":{"line":240,"column":14},"end":{"line":240,"column":35}}]},"35":{"type":"branch","line":302,"loc":{"start":{"line":302,"column":10},"end":{"line":305,"column":23}},"locations":[{"start":{"line":302,"column":10},"end":{"line":305,"column":23}}]},"36":{"type":"branch","line":309,"loc":{"start":{"line":309,"column":7},"end":{"line":309,"column":73}},"locations":[{"start":{"line":309,"column":7},"end":{"line":309,"column":73}}]},"37":{"type":"branch","line":250,"loc":{"start":{"line":250,"column":12},"end":{"line":267,"column":5}},"locations":[{"start":{"line":250,"column":12},"end":{"line":267,"column":5}}]},"38":{"type":"branch","line":251,"loc":{"start":{"line":251,"column":18},"end":{"line":254,"column":5}},"locations":[{"start":{"line":251,"column":18},"end":{"line":254,"column":5}}]},"39":{"type":"branch","line":254,"loc":{"start":{"line":254,"column":4},"end":{"line":266,"column":40}},"locations":[{"start":{"line":254,"column":4},"end":{"line":266,"column":40}}]},"40":{"type":"branch","line":258,"loc":{"start":{"line":258,"column":12},"end":{"line":260,"column":7}},"locations":[{"start":{"line":258,"column":12},"end":{"line":260,"column":7}}]},"41":{"type":"branch","line":259,"loc":{"start":{"line":259,"column":37},"end":{"line":259,"column":47}},"locations":[{"start":{"line":259,"column":37},"end":{"line":259,"column":47}}]},"42":{"type":"branch","line":259,"loc":{"start":{"line":259,"column":41},"end":{"line":259,"column":64}},"locations":[{"start":{"line":259,"column":41},"end":{"line":259,"column":64}}]},"43":{"type":"branch","line":261,"loc":{"start":{"line":261,"column":13},"end":{"line":265,"column":7}},"locations":[{"start":{"line":261,"column":13},"end":{"line":265,"column":7}}]},"44":{"type":"branch","line":266,"loc":{"start":{"line":266,"column":15},"end":{"line":266,"column":38}},"locations":[{"start":{"line":266,"column":15},"end":{"line":266,"column":38}}]},"45":{"type":"branch","line":270,"loc":{"start":{"line":270,"column":27},"end":{"line":273,"column":5}},"locations":[{"start":{"line":270,"column":27},"end":{"line":273,"column":5}}]},"46":{"type":"branch","line":271,"loc":{"start":{"line":271,"column":37},"end":{"line":272,"column":63}},"locations":[{"start":{"line":271,"column":37},"end":{"line":272,"column":63}}]},"47":{"type":"branch","line":275,"loc":{"start":{"line":275,"column":26},"end":{"line":275,"column":51}},"locations":[{"start":{"line":275,"column":26},"end":{"line":275,"column":51}}]},"48":{"type":"branch","line":278,"loc":{"start":{"line":278,"column":4},"end":{"line":278,"column":63}},"locations":[{"start":{"line":278,"column":4},"end":{"line":278,"column":63}}]},"49":{"type":"branch","line":278,"loc":{"start":{"line":278,"column":46},"end":{"line":278,"column":59}},"locations":[{"start":{"line":278,"column":46},"end":{"line":278,"column":59}}]},"50":{"type":"branch","line":298,"loc":{"start":{"line":298,"column":27},"end":{"line":299,"column":91}},"locations":[{"start":{"line":298,"column":27},"end":{"line":299,"column":91}}]},"51":{"type":"branch","line":325,"loc":{"start":{"line":325,"column":28},"end":{"line":328,"column":20}},"locations":[{"start":{"line":325,"column":28},"end":{"line":328,"column":20}}]},"52":{"type":"branch","line":330,"loc":{"start":{"line":330,"column":27},"end":{"line":333,"column":20}},"locations":[{"start":{"line":330,"column":27},"end":{"line":333,"column":20}}]}},"b":{"0":[10],"1":[0],"2":[1],"3":[6],"4":[0],"5":[12],"6":[2],"7":[10],"8":[0],"9":[0],"10":[0],"11":[0],"12":[16],"13":[1],"14":[15],"15":[47],"16":[47],"17":[0],"18":[0],"19":[0],"20":[47],"21":[0],"22":[38],"23":[9],"24":[47],"25":[47],"26":[9],"27":[47],"28":[38],"29":[9],"30":[0],"31":[47],"32":[41],"33":[36],"34":[0],"35":[12],"36":[0],"37":[16],"38":[6],"39":[10],"40":[6],"41":[5],"42":[1],"43":[4],"44":[10],"45":[31],"46":[0],"47":[16],"48":[31],"49":[60],"50":[45],"51":[19],"52":[32]},"fnMap":{"0":{"name":"fetchHorizonTransactions","decl":{"start":{"line":52,"column":0},"end":{"line":81,"column":1}},"loc":{"start":{"line":52,"column":0},"end":{"line":81,"column":1}},"line":52},"1":{"name":"buildColumns","decl":{"start":{"line":104,"column":0},"end":{"line":211,"column":1}},"loc":{"start":{"line":104,"column":0},"end":{"line":211,"column":1}},"line":104},"2":{"name":"valueFormatter","decl":{"start":{"line":114,"column":22},"end":{"line":115,"column":95}},"loc":{"start":{"line":114,"column":22},"end":{"line":115,"column":95}},"line":114},"3":{"name":"renderCell","decl":{"start":{"line":123,"column":18},"end":{"line":129,"column":10}},"loc":{"start":{"line":123,"column":18},"end":{"line":129,"column":10}},"line":123},"4":{"name":"sortComparator","decl":{"start":{"line":137,"column":22},"end":{"line":137,"column":78}},"loc":{"start":{"line":137,"column":22},"end":{"line":137,"column":78}},"line":137},"5":{"name":"renderCell","decl":{"start":{"line":138,"column":18},"end":{"line":149,"column":8}},"loc":{"start":{"line":138,"column":18},"end":{"line":149,"column":8}},"line":138},"6":{"name":"renderCell","decl":{"start":{"line":162,"column":18},"end":{"line":165,"column":21}},"loc":{"start":{"line":162,"column":18},"end":{"line":165,"column":21}},"line":162},"7":{"name":"renderCell","decl":{"start":{"line":174,"column":18},"end":{"line":177,"column":21}},"loc":{"start":{"line":174,"column":18},"end":{"line":177,"column":21}},"line":174},"8":{"name":"renderCell","decl":{"start":{"line":185,"column":18},"end":{"line":190,"column":10}},"loc":{"start":{"line":185,"column":18},"end":{"line":190,"column":10}},"line":185},"9":{"name":"renderCell","decl":{"start":{"line":198,"column":18},"end":{"line":207,"column":15}},"loc":{"start":{"line":198,"column":18},"end":{"line":207,"column":15}},"line":198},"10":{"name":"onClick","decl":{"start":{"line":204,"column":19},"end":{"line":204,"column":46}},"loc":{"start":{"line":204,"column":19},"end":{"line":204,"column":46}},"line":204},"11":{"name":"TransactionHistory","decl":{"start":{"line":233,"column":7},"end":{"line":347,"column":1}},"loc":{"start":{"line":233,"column":7},"end":{"line":347,"column":1}},"line":233},"12":{"name":"onChange","decl":{"start":{"line":294,"column":20},"end":{"line":294,"column":57}},"loc":{"start":{"line":294,"column":20},"end":{"line":294,"column":57}},"line":294},"13":{"name":"loadingOverlay","decl":{"start":{"line":325,"column":28},"end":{"line":328,"column":20}},"loc":{"start":{"line":325,"column":28},"end":{"line":328,"column":20}},"line":325},"14":{"name":"noRowsOverlay","decl":{"start":{"line":330,"column":27},"end":{"line":333,"column":20}},"loc":{"start":{"line":330,"column":27},"end":{"line":333,"column":20}},"line":330}},"f":{"0":10,"1":16,"2":47,"3":47,"4":0,"5":47,"6":47,"7":47,"8":47,"9":47,"10":0,"11":41,"12":0,"13":19,"14":32}} +} diff --git a/frontend/coverage/favicon.png b/frontend/coverage/favicon.png new file mode 100644 index 00000000..c1525b81 Binary files /dev/null and b/frontend/coverage/favicon.png differ diff --git a/frontend/coverage/index.html b/frontend/coverage/index.html new file mode 100644 index 00000000..446bc112 --- /dev/null +++ b/frontend/coverage/index.html @@ -0,0 +1,146 @@ + + + + + + Code coverage report for All files + + + + + + + + + +
+
+

All files

+
+ +
+ 99.84% + Statements + 634/635 +
+ + +
+ 87.01% + Branches + 134/154 +
+ + +
+ 91.66% + Functions + 33/36 +
+ + +
+ 99.84% + Lines + 634/635 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
ContributionFlow.tsx +
+
100%226/22695.58%65/68100%12/12100%226/226
GroupCard.tsx +
+
100%166/16690.9%30/33100%9/9100%166/166
TransactionHistory.tsx +
+
99.58%242/24373.58%39/5380%12/1599.58%242/243
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/prettify.css b/frontend/coverage/prettify.css new file mode 100644 index 00000000..b317a7cd --- /dev/null +++ b/frontend/coverage/prettify.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/frontend/coverage/prettify.js b/frontend/coverage/prettify.js new file mode 100644 index 00000000..b3225238 --- /dev/null +++ b/frontend/coverage/prettify.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/frontend/coverage/sort-arrow-sprite.png b/frontend/coverage/sort-arrow-sprite.png new file mode 100644 index 00000000..6ed68316 Binary files /dev/null and b/frontend/coverage/sort-arrow-sprite.png differ diff --git a/frontend/coverage/sorter.js b/frontend/coverage/sorter.js new file mode 100644 index 00000000..4ed70ae5 --- /dev/null +++ b/frontend/coverage/sorter.js @@ -0,0 +1,210 @@ +/* eslint-disable */ +var addSorting = (function() { + 'use strict'; + var cols, + currentSort = { + index: 0, + desc: false + }; + + // returns the summary table element + function getTable() { + return document.querySelector('.coverage-summary'); + } + // returns the thead element of the summary table + function getTableHeader() { + return getTable().querySelector('thead tr'); + } + // returns the tbody element of the summary table + function getTableBody() { + return getTable().querySelector('tbody'); + } + // returns the th element for nth column + function getNthColumn(n) { + return getTableHeader().querySelectorAll('th')[n]; + } + + function onFilterInput() { + const searchValue = document.getElementById('fileSearch').value; + const rows = document.getElementsByTagName('tbody')[0].children; + + // Try to create a RegExp from the searchValue. If it fails (invalid regex), + // it will be treated as a plain text search + let searchRegex; + try { + searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive + } catch (error) { + searchRegex = null; + } + + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + let isMatch = false; + + if (searchRegex) { + // If a valid regex was created, use it for matching + isMatch = searchRegex.test(row.textContent); + } else { + // Otherwise, fall back to the original plain text search + isMatch = row.textContent + .toLowerCase() + .includes(searchValue.toLowerCase()); + } + + row.style.display = isMatch ? '' : 'none'; + } + } + + // loads the search box + function addSearchBox() { + var template = document.getElementById('filterTemplate'); + var templateClone = template.content.cloneNode(true); + templateClone.getElementById('fileSearch').oninput = onFilterInput; + template.parentElement.appendChild(templateClone); + } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll('th'), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute('data-col'), + sortable: !colNode.getAttribute('data-nosort'), + type: colNode.getAttribute('data-type') || 'string' + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === 'number'; + colNode.innerHTML = + colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll('td'), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute('data-value'); + if (col.type === 'number') { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll('tr'), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function(a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector('.coverage-summary tbody'), + rowNodes = tableBody.querySelectorAll('tr'), + rows = [], + i; + + if (desc) { + finalSorter = function(a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc + ? ' sorted-desc' + : ' sorted'; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function() { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i = 0; i < cols.length; i += 1) { + if (cols[i].sortable) { + // add the click event handler on the th so users + // dont have to click on those tiny arrows + el = getNthColumn(i).querySelector('.sorter').parentElement; + if (el.addEventListener) { + el.addEventListener('click', ithSorter(i)); + } else { + el.attachEvent('onclick', ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function() { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener('load', addSorting); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index de680d29..9df7df7f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.14.1", "@mui/icons-material": "^7.3.9", "@mui/material": "^7.3.9", + "@mui/x-data-grid": "^7.28.3", "@stellar/freighter-api": "^6.0.1", "@stellar/stellar-sdk": "^14.5.0", "@tanstack/react-query": "^5.100.14", @@ -761,9 +762,9 @@ "license": "MIT" }, "node_modules/@coinbase/cdp-sdk": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/@coinbase/cdp-sdk/-/cdp-sdk-1.50.0.tgz", - "integrity": "sha512-lKK6aC2z8q8C3IA39unNuWc8lgM0hU9mSqkdd7Bncf5xvT28f8G6upexFtJweNwxkeAJwiLSgBkwOhqMK2/OGQ==", + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/@coinbase/cdp-sdk/-/cdp-sdk-1.51.0.tgz", + "integrity": "sha512-XK8+OXDER1jirYpuiOct4ij65ODQ31LsmyRrZi/J7zF4GB89qxWZ0KPfAdsqJMP7VvE4no+Q++MKkQtAJUBoyg==", "license": "MIT", "optional": true, "dependencies": { @@ -1019,6 +1020,21 @@ "node": ">=10" } }, + "node_modules/@creit.tech/stellar-wallets-kit/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/@creit.tech/xbull-wallet-connect": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@creit.tech/xbull-wallet-connect/-/xbull-wallet-connect-0.4.0.tgz", @@ -1977,19 +1993,19 @@ } }, "node_modules/@ethereumjs/common": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-10.1.1.tgz", - "integrity": "sha512-NefPzPlrJ9w+NWVe06P+sHZQU98E1AEU9vhiHJEVT2wEcNBC1YX6hON9+smrfbn86C4U1pb2zbvjhkF+n/LKBw==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-10.1.2.tgz", + "integrity": "sha512-whWnhqAxwpDy4zWkM6rqMzb8nioZJpiys01N57+HDyanvK5IRzodV5tdMRDt66PD5vDjl2c9K5UcB039gU2Oyw==", "license": "MIT", "dependencies": { - "@ethereumjs/util": "^10.1.1", + "@ethereumjs/util": "^10.1.2", "eventemitter3": "^5.0.1" } }, "node_modules/@ethereumjs/rlp": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-10.1.1.tgz", - "integrity": "sha512-jbnWTEwcpoY+gE0r+wxfDG9zgiu54DcTcwnc9sX3DsqKR4l5K7x2V8mQL3Et6hURa4DuT9g7z6ukwpBLFchszg==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-10.1.2.tgz", + "integrity": "sha512-T5Zt6C2pd02Wd88Q9A5/UX+He1Q2Y1LntHxz/038tfbUMiqby4fYSSTLEDx+TEfJqw1BsJSBY/TSu6goUzlk+w==", "license": "MPL-2.0", "bin": { "rlp": "bin/rlp.cjs" @@ -1999,14 +2015,14 @@ } }, "node_modules/@ethereumjs/tx": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-10.1.1.tgz", - "integrity": "sha512-Kz8GWIKQjEQB60ko9hsYDX3rZMHZZOTcmm6OFl855Lu3padVnf5ZactUKM6nmWPsumHED5bWDjO32novZd1zyw==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-10.1.2.tgz", + "integrity": "sha512-bAYK3YaYkk+auzxGfZSRVDbLHvboJNTx8/tV6jaqgPVlrA1QKEEADDEp/EGz+KI4NQmTGxEtXZ8tV/WjniRNww==", "license": "MPL-2.0", "dependencies": { - "@ethereumjs/common": "^10.1.1", - "@ethereumjs/rlp": "^10.1.1", - "@ethereumjs/util": "^10.1.1", + "@ethereumjs/common": "^10.1.2", + "@ethereumjs/rlp": "^10.1.2", + "@ethereumjs/util": "^10.1.2", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1" }, @@ -2042,12 +2058,12 @@ } }, "node_modules/@ethereumjs/util": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-10.1.1.tgz", - "integrity": "sha512-r2EhaeEmLZXVs1dT2HJFQysAkr63ZWATu/9tgYSp1IlvjvwyC++DLg5kCDwMM49HBq3sOAhrPnXkoqf9DV2gbw==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-10.1.2.tgz", + "integrity": "sha512-UPBgXtHHfQugoXOSAoeG3jdmPbl37cwV9y3XqTPAnw8tJj8np14TPV2uc5lOs7C2LMF9Ubn66zyaiYxgwGppng==", "license": "MPL-2.0", "dependencies": { - "@ethereumjs/rlp": "^10.1.1", + "@ethereumjs/rlp": "^10.1.2", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1" }, @@ -2261,36 +2277,6 @@ "node": ">=18" } }, - "node_modules/@mui/icons-material": { - "version": "7.3.9", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.9.tgz", - "integrity": "sha512-BT+zPJXss8Hg/oEMRmHl17Q97bPACG4ufFSfGEdhiE96jOyR5Dz1ty7ZWt1fVGR0y1p+sSgEwQT/MNZQmoWDCw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.28.6" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@mui/material": "^7.3.9", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/material": { - "version": "7.3.9", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.9.tgz", - "integrity": "sha512-I8yO3t4T0y7bvDiR1qhIN6iBWZOTBfVOnmLlM7K6h3dx5YX2a7rnkuXzc2UkZaqhxY9NgTnEbdPlokR1RxCNRQ==", "node_modules/@inquirer/expand": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-3.0.1.tgz", @@ -2739,6 +2725,32 @@ "url": "https://opencollective.com/mui-org" } }, + "node_modules/@mui/icons-material": { + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.11.tgz", + "integrity": "sha512-+hz5ilwHZ3djd5es3sCErLioqe/NhZcYTsV/TNXZAMdJdb23F4xzJjqnnZdnurc3S1+ietcssRNqieOhPQLZ7Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.3.11", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/material": { "version": "7.3.11", "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.11.tgz", @@ -2936,6 +2948,64 @@ } } }, + "node_modules/@mui/x-data-grid": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.29.13.tgz", + "integrity": "sha512-XHrZTvpa61eqSgUIevDzXYfYCbI7cbN/aRxSCaw2cZzQZ/USdpECYbnTEhgw5XgUlvkAK0mItKZmgKM4X7zT+Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0 || ^7.0.0", + "@mui/x-internals": "7.29.0", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "reselect": "^5.1.1", + "use-sync-external-store": "^1.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0", + "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/x-internals": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.29.0.tgz", + "integrity": "sha512-+Gk6VTZIFD70XreWvdXBwKd8GZ2FlSCuecQFzm6znwqXg1ZsndavrhG9tkxpxo2fM1Zf7Tk8+HcOO0hCbhTQFA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0 || ^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@near-js/accounts": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@near-js/accounts/-/accounts-1.4.1.tgz", @@ -10139,6 +10209,21 @@ "ws": "^7.5.1" } }, + "node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/ws": { "version": "7.5.11", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", @@ -13131,9 +13216,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.363", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.363.tgz", - "integrity": "sha512-VjUKPyWzGnT1fujlkEGC/BvN70Hh70KXtAqcmniXviYlJC/ivcT+BWGPyxWVbJZLfvtKR6dqg1L7T7pgAMBtWA==", + "version": "1.5.364", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.364.tgz", + "integrity": "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==", "dev": true, "license": "ISC" }, @@ -15636,6 +15721,21 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, + "node_modules/jayson/node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/jayson/node_modules/ws": { "version": "7.5.11", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", @@ -16351,6 +16451,22 @@ "url": "https://github.com/yeoman/update-notifier?sponsor=1" } }, + "node_modules/lighthouse/node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/lighthouse/node_modules/ws": { "version": "7.5.11", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", @@ -18668,6 +18784,22 @@ "dev": true, "license": "MIT" }, + "node_modules/puppeteer/node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/puppeteer/node_modules/ws": { "version": "7.5.11", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", @@ -19001,9 +19133,9 @@ } }, "node_modules/react-big-calendar": { - "version": "1.19.4", - "resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-1.19.4.tgz", - "integrity": "sha512-FrvbDx2LF6JAWFD96LU1jjloppC5OgIvMYUYIPzAw5Aq+ArYFPxAjLqXc4DyxfsQDN0TJTMuS/BIbcSB7Pg0YA==", + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-1.19.6.tgz", + "integrity": "sha512-2vCG/LBuNOOok4Olet4S7M6l11DjCO6RyWwqGRkZcR0cTOWEKVCwpqe5r24NobmVKPQV6nU3/DZGoAkrOypRlw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.7", @@ -19413,6 +19545,12 @@ "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/recharts/node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -19515,9 +19653,9 @@ "license": "ISC" }, "node_modules/reselect": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", - "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", "license": "MIT" }, "node_modules/resolve": { @@ -20494,9 +20632,9 @@ "license": "MIT" }, "node_modules/systeminformation": { - "version": "5.31.6", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.6.tgz", - "integrity": "sha512-Uv2b2uGGM6ns+26czgW2cYRabYdnswM0ddSOOlryHOaelzsmDSet1iM/NT7VOYxW8x/BW+HkY+b1Ve2pLTSGSA==", + "version": "5.31.7", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.7.tgz", + "integrity": "sha512-/8NC53e5nP9nmhn42/ncdOkyJnOoue/Vy+tJOyUGd1Yv66G069wK4rrziwhrqDETgk78CudTQupw5z19S5uoZw==", "dev": true, "license": "MIT", "os": [ diff --git a/frontend/src/test/ContributionFlow.test.tsx b/frontend/src/test/ContributionFlow.test.tsx new file mode 100644 index 00000000..8c6c8904 --- /dev/null +++ b/frontend/src/test/ContributionFlow.test.tsx @@ -0,0 +1,378 @@ +import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import userEvent from '@testing-library/user-event'; +import { ContributionFlow } from '../components/ContributionFlow'; + +const LONG_TIMEOUT = 10000; + +beforeEach(() => { + Object.defineProperty(window, 'AudioContext', { + writable: true, + value: vi.fn().mockImplementation(() => ({ + createOscillator: () => ({ + connect: vi.fn(), + type: '', + frequency: { value: 0 }, + start: vi.fn(), + stop: vi.fn(), + }), + createGain: () => ({ + connect: vi.fn(), + gain: { setValueAtTime: vi.fn(), exponentialRampToValueAtTime: vi.fn() }, + }), + destination: {}, + currentTime: 0, + })), + }); +}); + +afterEach(() => { + vi.restoreAllMocks(); +}); + +function renderFlow(props = {}) { + const defaultProps = { + cycleId: 3, + walletAddress: 'GABC1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', + }; + return render(); +} + +describe('ContributionFlow — initial state', () => { + it('renders the form with amount input', () => { + renderFlow(); + expect(screen.getByLabelText(/Contribution Amount/)).toBeInTheDocument(); + }); + + it('shows Contribute button', () => { + renderFlow(); + expect(screen.getByRole('button', { name: /contribute/i })).toBeInTheDocument(); + }); + + it('does not show wallet warning when walletAddress is provided', () => { + renderFlow(); + expect(screen.queryByText(/Connect your wallet/i)).not.toBeInTheDocument(); + }); + + it('shows wallet warning when walletAddress is not provided', () => { + renderFlow({ walletAddress: undefined }); + expect(screen.getByText(/Connect your wallet/i)).toBeInTheDocument(); + }); + + it('disables input and submit when walletAddress is missing', () => { + renderFlow({ walletAddress: undefined }); + expect(screen.getByLabelText(/Contribution Amount/)).toBeDisabled(); + expect(screen.getByRole('button', { name: /contribute/i })).toBeDisabled(); + }); + + it('shows helper text with min/max values', () => { + renderFlow({ minAmount: 5, maxAmount: 5000 }); + const input = screen.getByLabelText(/Contribution Amount/); + expect(input).toHaveAccessibleDescription(/Min: 5.*Max: 5,000/); + }); + + it('pre-fills amount when defaultAmount is provided', () => { + renderFlow({ defaultAmount: 50 }); + const input = screen.getByLabelText(/Contribution Amount/); + expect(input).toHaveValue(50); + }); + + it('renders quick-select chips when defaultAmount is provided', () => { + renderFlow({ defaultAmount: 100 }); + expect(screen.getByText('50 XLM')).toBeInTheDocument(); + expect(screen.getByText('100 XLM')).toBeInTheDocument(); + expect(screen.getByText('200 XLM')).toBeInTheDocument(); + }); + + it('does not render quick-select chips without defaultAmount', () => { + renderFlow(); + expect(screen.queryByText('50 XLM')).not.toBeInTheDocument(); + }); + + it('does not show any status alert initially', () => { + renderFlow(); + expect(screen.queryByRole('alert')).not.toBeInTheDocument(); + }); +}); + +describe('ContributionFlow — amount validation', () => { + it('shows error for empty amount on submit', async () => { + const user = userEvent.setup(); + renderFlow({ minAmount: 1, maxAmount: 100 }); + await user.click(screen.getByRole('button', { name: /contribute/i })); + expect(screen.getByText('Please enter a valid amount.')).toBeInTheDocument(); + }); + + it('shows error for negative amount on submit', async () => { + const user = userEvent.setup(); + renderFlow({ minAmount: 1, maxAmount: 100 }); + const input = screen.getByLabelText(/Contribution Amount/); + await user.type(input, '-10'); + await user.click(screen.getByRole('button', { name: /contribute/i })); + expect(screen.getByText('Amount must be greater than 0.')).toBeInTheDocument(); + }); + + it('shows error for amount below minimum', async () => { + const user = userEvent.setup(); + renderFlow({ minAmount: 10, maxAmount: 100 }); + const input = screen.getByLabelText(/Contribution Amount/); + await user.type(input, '5'); + await user.click(screen.getByRole('button', { name: /contribute/i })); + expect(screen.getByText('Minimum contribution is 10 XLM.')).toBeInTheDocument(); + }); + + it('shows error for amount above maximum', async () => { + const user = userEvent.setup(); + renderFlow({ minAmount: 1, maxAmount: 50 }); + const input = screen.getByLabelText(/Contribution Amount/); + await user.type(input, '100'); + await user.click(screen.getByRole('button', { name: /contribute/i })); + expect(screen.getByText('Maximum contribution is 50 XLM.')).toBeInTheDocument(); + }); + + it('clears field error when user types after validation failure', async () => { + const user = userEvent.setup(); + renderFlow({ minAmount: 1, maxAmount: 50 }); + const input = screen.getByLabelText(/Contribution Amount/); + await user.type(input, '100'); + await user.click(screen.getByRole('button', { name: /contribute/i })); + expect(screen.getByText('Maximum contribution is 50 XLM.')).toBeInTheDocument(); + await user.clear(input); + await user.type(input, '25'); + expect(screen.queryByText('Maximum contribution is 50 XLM.')).not.toBeInTheDocument(); + }); +}); + +describe('ContributionFlow — quick-select chips', () => { + it('sets amount when chip is clicked', async () => { + const user = userEvent.setup(); + renderFlow({ defaultAmount: 100 }); + await user.click(screen.getByText('200 XLM')); + const input = screen.getByLabelText(/Contribution Amount/); + expect(input).toHaveValue(200); + }); + + it('highlights selected chip', async () => { + const user = userEvent.setup(); + renderFlow({ defaultAmount: 100 }); + await user.click(screen.getByText('200 XLM')); + const chip = screen.getByText('200 XLM'); + expect(chip).toBeInTheDocument(); + }); +}); + +describe('ContributionFlow — confirmation dialog', () => { + it('opens confirmation dialog on valid submit', async () => { + const user = userEvent.setup(); + renderFlow({ cycleId: 5, defaultAmount: 50, minAmount: 1, maxAmount: 1000 }); + const input = screen.getByLabelText(/Contribution Amount/); + await user.clear(input); + await user.type(input, '75'); + await user.click(screen.getByRole('button', { name: /contribute/i })); + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + expect(screen.getByText(/Cycle #5/)).toBeInTheDocument(); + }); + + it('shows amount in confirmation dialog', async () => { + const user = userEvent.setup(); + renderFlow({ defaultAmount: 50, minAmount: 1, maxAmount: 1000 }); + await user.click(screen.getByRole('button', { name: /contribute/i })); + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + const amounts = screen.getAllByText(/50/); + expect(amounts.length).toBeGreaterThanOrEqual(1); + }); + + it('cancels confirmation dialog without submitting', async () => { + const user = userEvent.setup(); + renderFlow({ minAmount: 1, maxAmount: 1000, defaultAmount: 25 }); + await user.click(screen.getByRole('button', { name: /contribute/i })); + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + await user.click(screen.getByRole('button', { name: /cancel/i })); + await waitFor(() => { + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + }); +}); + +const LONG_TEST_TIMEOUT = 15000; + +describe('ContributionFlow — success flow', () => { + beforeEach(() => { + vi.spyOn(Math, 'random').mockReturnValue(0.9); + }); + + async function confirmSuccess(user: ReturnType) { + await user.click(screen.getByRole('button', { name: /contribute/i })); + await waitFor(() => { expect(screen.getByRole('dialog')).toBeInTheDocument(); }, { timeout: LONG_TIMEOUT }); + await user.click(screen.getByRole('button', { name: /confirm & sign/i })); + await waitFor(() => { + const headings = screen.getAllByRole('heading', { name: /Contribution Successful/i }); + expect(headings.length).toBeGreaterThanOrEqual(1); + }, { timeout: LONG_TIMEOUT }); + } + + it('shows success message after signing', { timeout: LONG_TEST_TIMEOUT }, async () => { + const onSuccess = vi.fn(); + const user = userEvent.setup(); + renderFlow({ cycleId: 2, defaultAmount: 100, minAmount: 1, maxAmount: 1000, onSuccess }); + await confirmSuccess(user); + expect(screen.getByText(/100 XLM contributed to Cycle #2/)).toBeInTheDocument(); + }); + + it('calls onSuccess with tx hash and amount', { timeout: LONG_TEST_TIMEOUT }, async () => { + const onSuccess = vi.fn(); + const user = userEvent.setup(); + renderFlow({ cycleId: 2, defaultAmount: 100, minAmount: 1, maxAmount: 1000, onSuccess }); + await confirmSuccess(user); + expect(onSuccess).toHaveBeenCalledWith(expect.stringMatching(/^tx_/), 100); + }); + + it('shows Stellar Explorer link after success', { timeout: LONG_TEST_TIMEOUT }, async () => { + const user = userEvent.setup(); + renderFlow({ defaultAmount: 50, minAmount: 1, maxAmount: 1000 }); + await confirmSuccess(user); + const links = screen.getAllByText(/View on Stellar Explorer/); + expect(links.length).toBeGreaterThanOrEqual(1); + }); + + it('shows Make Another Contribution button after success', { timeout: LONG_TEST_TIMEOUT }, async () => { + const user = userEvent.setup(); + renderFlow({ defaultAmount: 50, minAmount: 1, maxAmount: 1000 }); + await confirmSuccess(user); + expect(screen.getByRole('button', { name: /Make Another Contribution/i })).toBeInTheDocument(); + }); + + it('resets form when Make Another Contribution is clicked', { timeout: LONG_TEST_TIMEOUT }, async () => { + const user = userEvent.setup(); + renderFlow({ defaultAmount: 50, minAmount: 1, maxAmount: 1000 }); + await confirmSuccess(user); + await user.click(screen.getByRole('button', { name: /Make Another Contribution/i })); + expect(screen.getByRole('button', { name: /contribute/i })).toBeInTheDocument(); + }); + + it('shows New button in status banner and resets on click', { timeout: LONG_TEST_TIMEOUT }, async () => { + const user = userEvent.setup(); + renderFlow({ defaultAmount: 50, minAmount: 1, maxAmount: 1000 }); + await confirmSuccess(user); + await user.click(screen.getByRole('button', { name: /New/i })); + expect(screen.getByRole('button', { name: /contribute/i })).toBeInTheDocument(); + }); +}); + +describe('ContributionFlow — error and retry', () => { + async function confirmError(user: ReturnType) { + await user.click(screen.getByRole('button', { name: /contribute/i })); + await waitFor(() => { expect(screen.getByRole('dialog')).toBeInTheDocument(); }, { timeout: LONG_TIMEOUT }); + await user.click(screen.getByRole('button', { name: /confirm & sign/i })); + await waitFor(() => { expect(screen.getByText(/Transaction failed/)).toBeInTheDocument(); }, { timeout: LONG_TIMEOUT }); + } + + it('shows error message when user rejects transaction', async () => { + vi.spyOn(Math, 'random').mockReturnValue(0.04); + const onError = vi.fn(); + const user = userEvent.setup(); + renderFlow({ defaultAmount: 50, minAmount: 1, maxAmount: 1000, onError }); + await confirmError(user); + expect(onError).toHaveBeenCalled(); + expect(onError).toHaveBeenCalledWith(expect.any(Error)); + }); + + it('shows error status alert with retry button', async () => { + vi.spyOn(Math, 'random').mockReturnValue(0.04); + const user = userEvent.setup(); + renderFlow({ defaultAmount: 50, minAmount: 1, maxAmount: 1000 }); + await confirmError(user); + expect(screen.getByRole('button', { name: /retry/i })).toBeInTheDocument(); + }); + + it('shows Try Again button next to form after error', async () => { + vi.spyOn(Math, 'random').mockReturnValue(0.04); + const user = userEvent.setup(); + renderFlow({ defaultAmount: 50, minAmount: 1, maxAmount: 1000 }); + await confirmError(user); + expect(screen.getByRole('button', { name: /Try Again/i })).toBeInTheDocument(); + }); + + it('retry clears error and shows form again', async () => { + vi.spyOn(Math, 'random').mockReturnValue(0.04); + const user = userEvent.setup(); + renderFlow({ defaultAmount: 50, minAmount: 1, maxAmount: 1000 }); + await confirmError(user); + await user.click(screen.getByRole('button', { name: /retry/i })); + expect(screen.queryByText(/Transaction failed/)).not.toBeInTheDocument(); + }); + + it('succeeds after retry when random returns high value', { timeout: LONG_TEST_TIMEOUT }, async () => { + const mockMath = vi.spyOn(Math, 'random'); + mockMath.mockReturnValue(0.04); + const onSuccess = vi.fn(); + const user = userEvent.setup(); + renderFlow({ defaultAmount: 50, minAmount: 1, maxAmount: 1000, onSuccess }); + await confirmError(user); + mockMath.mockReset(); + mockMath.mockReturnValue(0.9); + await user.click(screen.getByRole('button', { name: /retry/i })); + await user.click(screen.getByRole('button', { name: /contribute/i })); + await waitFor(() => { expect(screen.getByRole('dialog')).toBeInTheDocument(); }, { timeout: LONG_TIMEOUT }); + await user.click(screen.getByRole('button', { name: /confirm & sign/i })); + await waitFor(() => { + const headings = screen.getAllByRole('heading', { name: /Contribution Successful/i }); + expect(headings.length).toBeGreaterThanOrEqual(1); + }, { timeout: LONG_TIMEOUT }); + expect(onSuccess).toHaveBeenCalled(); + }); +}); + +describe('ContributionFlow — disabled state', () => { + it('disables input and submit when disabled prop is true', () => { + renderFlow({ disabled: true }); + expect(screen.getByLabelText(/Contribution Amount/)).toBeDisabled(); + expect(screen.getByRole('button', { name: /contribute/i })).toBeDisabled(); + }); + + it('disables chips when disabled prop is true', () => { + renderFlow({ disabled: true, defaultAmount: 100 }); + const chips = screen.getAllByRole('button').filter(b => b.textContent?.includes('XLM')); + chips.forEach(chip => { + expect(chip).toHaveAttribute('aria-disabled', 'true'); + }); + }); +}); + +describe('ContributionFlow — custom min/max', () => { + it('accepts custom minAmount and maxAmount', async () => { + const user = userEvent.setup(); + renderFlow({ minAmount: 5, maxAmount: 25, defaultAmount: 10 }); + const input = screen.getByLabelText(/Contribution Amount/); + await user.clear(input); + await user.type(input, '30'); + await user.click(screen.getByRole('button', { name: /contribute/i })); + expect(screen.getByText('Maximum contribution is 25 XLM.')).toBeInTheDocument(); + await user.clear(input); + await user.type(input, '2'); + await user.click(screen.getByRole('button', { name: /contribute/i })); + expect(screen.getByText('Minimum contribution is 5 XLM.')).toBeInTheDocument(); + }); +}); + +describe('ContributionFlow — onError callback', () => { + it('calls onError when transaction fails', { timeout: LONG_TEST_TIMEOUT }, async () => { + vi.spyOn(Math, 'random').mockReturnValue(0.04); + const onError = vi.fn(); + const user = userEvent.setup(); + renderFlow({ defaultAmount: 50, minAmount: 1, maxAmount: 1000, onError }); + await user.click(screen.getByRole('button', { name: /contribute/i })); + await waitFor(() => { expect(screen.getByRole('dialog')).toBeInTheDocument(); }, { timeout: LONG_TIMEOUT }); + await user.click(screen.getByRole('button', { name: /confirm & sign/i })); + await waitFor(() => { expect(onError).toHaveBeenCalled(); }, { timeout: LONG_TIMEOUT }); + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ message: 'User rejected the transaction in wallet.' }), + ); + }); +}); diff --git a/frontend/src/test/GroupCard.test.tsx b/frontend/src/test/GroupCard.test.tsx index 44ce8da5..36282e24 100644 --- a/frontend/src/test/GroupCard.test.tsx +++ b/frontend/src/test/GroupCard.test.tsx @@ -1,8 +1,11 @@ -import { render, screen, fireEvent } from '@testing-library/react'; -import { describe, it, expect, vi } from 'vitest'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest'; import { MemoryRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { GroupCard } from '../components/GroupCard'; +import { fetchGroup } from '../utils/groupApi'; + +vi.mock('../utils/groupApi'); function wrapper({ children }: { children: React.ReactNode }) { const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }); @@ -22,7 +25,27 @@ function renderCard(props = {}) { return render(, { wrapper }); } -describe('GroupCard', () => { +const MOCK_FETCHED_GROUP = { + id: 'group-42', + name: 'Fetched Circle', + memberCount: 12, + contributionAmount: 10_000_000, + currency: 'XLM', + status: 'active', + currentCycle: 3, + startedAt: new Date('2026-01-01'), + cycleDuration: 86400, + description: 'A test group description', + imageUrl: 'https://example.com/img.png', + creator: 'GABC...', + maxMembers: 20, + minMembers: 3, + isActive: true, + started: true, + createdAt: new Date('2026-01-01'), +}; + +describe('GroupCard — static mode', () => { it('renders group name', () => { renderCard(); expect(screen.getByText('Alpha Savers')).toBeInTheDocument(); @@ -96,6 +119,11 @@ describe('GroupCard', () => { expect(screen.getByText('3')).toBeInTheDocument(); }); + it('defaults current cycle to 0 when not provided', () => { + renderCard(); + expect(screen.getByText('0')).toBeInTheDocument(); + }); + it('shows next payout date when provided', () => { const date = new Date('2026-08-01'); renderCard({ nextPayoutDate: date }); @@ -116,4 +144,173 @@ describe('GroupCard', () => { renderCard({ status: 'completed' }); expect(screen.getByText('completed')).toBeInTheDocument(); }); + + it('shows pending status badge', () => { + renderCard({ status: 'pending' }); + expect(screen.getByText('pending')).toBeInTheDocument(); + }); + + it('shows complete status badge', () => { + renderCard({ status: 'complete' }); + expect(screen.getByText('complete')).toBeInTheDocument(); + }); + + it('renders description when provided', () => { + renderCard({ description: 'A great savings group' }); + expect(screen.getByText('A great savings group')).toBeInTheDocument(); + }); + + it('renders image when imageUrl is provided', () => { + renderCard({ imageUrl: 'https://example.com/group.jpg' }); + const img = screen.getByRole('img'); + expect(img).toHaveAttribute('src', 'https://example.com/group.jpg'); + expect(img).toHaveAttribute('alt', 'Alpha Savers'); + }); + + it('applies className prop', () => { + const { container } = renderCard({ className: 'my-custom-class' }); + const card = container.querySelector('.group-card'); + expect(card).toHaveClass('my-custom-class'); + }); + + it('does not render description area when no description', () => { + const { container } = renderCard(); + expect(container.querySelector('.group-card-description')).toBeNull(); + }); + + it('does not render image when no imageUrl', () => { + renderCard(); + expect(screen.queryByRole('img')).not.toBeInTheDocument(); + }); + + it('renders View Details and Join Group buttons together', () => { + renderCard({ onViewDetails: vi.fn(), onJoin: vi.fn() }); + expect(screen.getByRole('button', { name: 'View Details' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Join Group' })).toBeInTheDocument(); + }); + + it('does not render View Details button when not provided', () => { + renderCard(); + expect(screen.queryByRole('button', { name: 'View Details' })).not.toBeInTheDocument(); + }); + + it('does not render Join Group button when not provided', () => { + renderCard(); + expect(screen.queryByRole('button', { name: 'Join Group' })).not.toBeInTheDocument(); + }); +}); + +describe('GroupCard — fetch mode', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('shows loading skeleton while fetching', () => { + (fetchGroup as Mock).mockReturnValue(new Promise(() => {})); + const { container } = render( + , + { wrapper }, + ); + const skeletonDivs = container.querySelectorAll('.MuiSkeleton-root'); + expect(skeletonDivs.length).toBeGreaterThan(0); + }); + + it('renders error state when fetch fails', async () => { + (fetchGroup as Mock).mockRejectedValue(new Error('Network error')); + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByRole('alert')).toBeInTheDocument(); + }); + expect(screen.getByText('Network error')).toBeInTheDocument(); + }); + + it('renders error state with default message when fetch returns null', async () => { + (fetchGroup as Mock).mockResolvedValue(null); + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByRole('alert')).toBeInTheDocument(); + }); + expect(screen.getByText('Failed to load group.')).toBeInTheDocument(); + }); + + it('renders group data after successful fetch', async () => { + (fetchGroup as Mock).mockResolvedValue(MOCK_FETCHED_GROUP); + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByText('Fetched Circle')).toBeInTheDocument(); + }); + expect(screen.getByText('1 XLM')).toBeInTheDocument(); + expect(screen.getByText('12')).toBeInTheDocument(); + expect(screen.getByText('3')).toBeInTheDocument(); + expect(screen.getByText('active')).toBeInTheDocument(); + }); + + it('passes className in fetch mode', async () => { + (fetchGroup as Mock).mockResolvedValue(MOCK_FETCHED_GROUP); + const { container } = render( + , + { wrapper }, + ); + await waitFor(() => { + expect(screen.getByText('Fetched Circle')).toBeInTheDocument(); + }); + const link = container.querySelector('a'); + expect(link).toHaveClass('custom-klass'); + }); + + it('renders as a link in fetch mode with groupId', async () => { + (fetchGroup as Mock).mockResolvedValue(MOCK_FETCHED_GROUP); + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByRole('link')).toBeInTheDocument(); + }); + expect(screen.getByRole('link')).toHaveAttribute('href', '/groups/group-42'); + }); + + it('renders description from fetched data', async () => { + (fetchGroup as Mock).mockResolvedValue(MOCK_FETCHED_GROUP); + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByText('A test group description')).toBeInTheDocument(); + }); + }); + + it('renders image from fetched data', async () => { + (fetchGroup as Mock).mockResolvedValue(MOCK_FETCHED_GROUP); + render(, { wrapper }); + await waitFor(() => { + const img = screen.getByRole('img'); + expect(img).toHaveAttribute('src', 'https://example.com/img.png'); + }); + }); + + it('handles onViewDetails callback in fetch mode', async () => { + const onViewDetails = vi.fn(); + (fetchGroup as Mock).mockResolvedValue(MOCK_FETCHED_GROUP); + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByRole('button', { name: 'View Details' })).toBeInTheDocument(); + }); + fireEvent.click(screen.getByRole('button', { name: 'View Details' })); + expect(onViewDetails).toHaveBeenCalledTimes(1); + }); + + it('handles onJoin callback in fetch mode', async () => { + const onJoin = vi.fn(); + (fetchGroup as Mock).mockResolvedValue(MOCK_FETCHED_GROUP); + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByRole('button', { name: 'Join Group' })).toBeInTheDocument(); + }); + fireEvent.click(screen.getByRole('button', { name: 'Join Group' })); + expect(onJoin).toHaveBeenCalledTimes(1); + }); + + it('shows error when Error is thrown without message', async () => { + (fetchGroup as Mock).mockRejectedValue({}); + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByText('Failed to load group.')).toBeInTheDocument(); + }); + }); }); diff --git a/frontend/src/test/TransactionHistory.test.tsx b/frontend/src/test/TransactionHistory.test.tsx new file mode 100644 index 00000000..279b0ac3 --- /dev/null +++ b/frontend/src/test/TransactionHistory.test.tsx @@ -0,0 +1,280 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { render, screen, waitFor, fireEvent } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { TransactionHistory } from '../components/TransactionHistory'; +import * as useWalletHook from '../hooks/useWallet'; + +vi.mock('../hooks/useWallet', () => ({ + useWallet: vi.fn().mockReturnValue({ activeAddress: null, network: 'TESTNET' }), +})); + +let prevClientHeight: PropertyDescriptor | undefined; +let prevClientWidth: PropertyDescriptor | undefined; + +function mockContainerDimensions() { + prevClientHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'clientHeight'); + prevClientWidth = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'clientWidth'); + Object.defineProperty(HTMLElement.prototype, 'clientHeight', { configurable: true, get() { return 480; } }); + Object.defineProperty(HTMLElement.prototype, 'clientWidth', { configurable: true, get() { return 900; } }); +} + +function restoreContainerDimensions() { + if (prevClientHeight) Object.defineProperty(HTMLElement.prototype, 'clientHeight', prevClientHeight); + if (prevClientWidth) Object.defineProperty(HTMLElement.prototype, 'clientWidth', prevClientWidth); +} + +beforeEach(() => { + global.ResizeObserver = class ResizeObserver { + private cb: ResizeObserverCallback; + constructor(cb: ResizeObserverCallback) { + this.cb = cb; + } + observe(target: Element) { + this.cb( + [{ contentRect: { height: 480, width: 900, x: 0, y: 0, top: 0, right: 900, bottom: 480, left: 0 } as DOMRectReadOnly, target } as ResizeObserverEntry], + this as unknown as ResizeObserver, + ); + } + unobserve() {} + disconnect() {} + }; + + mockContainerDimensions(); + + const mock = useWalletHook.useWallet as ReturnType; + mock.mockReturnValue({ activeAddress: null, network: 'TESTNET' }); +}); + +afterEach(() => { + vi.restoreAllMocks(); + restoreContainerDimensions(); +}); + +function mockHorizonResponse(overrides = {}) { + return { + _embedded: { + records: [ + { + id: '123', + type: 'payment', + created_at: '2026-04-20T10:30:00Z', + transaction_hash: 'abc123', + amount: '250.0000000', + asset_code: 'XLM', + asset_type: 'native', + from: 'GASEND...', + to: 'GARECV...', + memo: 'Group contribution', + }, + { + id: '456', + type: 'payment', + created_at: '2026-04-15T14:22:00Z', + transaction_hash: 'def456', + amount: '1000.0000000', + asset_code: 'USDC', + asset_type: 'credit_alphanum4', + from: 'GASEND2...', + to: 'GARECV2...', + memo: 'Payout', + }, + ], + }, + ...overrides, + }; +} + +describe('TransactionHistory — no wallet connected', () => { + it('renders title', () => { + render(); + expect(screen.getByText('Transaction History')).toBeInTheDocument(); + }); + + it('shows demo data hint when no address', () => { + render(); + expect(screen.getByText(/Showing demo data/i)).toBeInTheDocument(); + }); + + it('renders mock transaction data from MOCK_TRANSACTIONS', async () => { + const { container } = render(); + await waitFor(() => { + expect(container.querySelector('[aria-rowcount="6"]')).toBeInTheDocument(); + }); + }); + + it('shows no demo hint when address is provided', () => { + const mock = useWalletHook.useWallet as ReturnType; + mock.mockReturnValue({ activeAddress: 'GABC1234567890', network: 'TESTNET' }); + render(); + expect(screen.queryByText(/Showing demo data/i)).not.toBeInTheDocument(); + }); +}); + +describe('TransactionHistory — type filter', () => { + it('renders type filter dropdown with All types option', () => { + render(); + expect(screen.getByText('All types')).toBeInTheDocument(); + }); + + it('renders unique type options from transaction data', async () => { + const { container } = render(); + await waitFor(() => { + expect(container.querySelector('[aria-rowcount="6"]')).toBeInTheDocument(); + }); + const trigger = screen.getByRole('combobox', { name: /type/i }); + expect(trigger).toBeInTheDocument(); + }); +}); + +describe('TransactionHistory — with address and Horizon fetch', () => { + const originalFetch = global.fetch; + let mockFetch: ReturnType; + + beforeEach(() => { + mockFetch = vi.fn(); + global.fetch = mockFetch; + const mock = useWalletHook.useWallet as ReturnType; + mock.mockReturnValue({ activeAddress: 'GABC1234567890', network: 'TESTNET' }); + }); + + afterEach(() => { + global.fetch = originalFetch; + }); + + it('fetches transactions from Horizon on mount', async () => { + mockFetch.mockResolvedValue({ + ok: true, + json: () => Promise.resolve(mockHorizonResponse()), + }); + render(); + await waitFor(() => { + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining('/accounts/GABC1234567890/payments'), + ); + }); + }); + + it('renders fetched transaction data', async () => { + mockFetch.mockResolvedValue({ + ok: true, + json: () => Promise.resolve(mockHorizonResponse()), + }); + const { container } = render(); + await waitFor(() => { + expect(mockFetch).toHaveBeenCalled(); + }); + await waitFor(() => { + const el = container.querySelector('[aria-rowcount]'); + const count = el?.getAttribute('aria-rowcount'); + expect(count).toBe('3'); + }); + }); + + it('falls back to mock data when Horizon fetch returns empty', async () => { + const { container } = render(); + await waitFor(() => { + expect(container.querySelector('[aria-rowcount="6"]')).toBeInTheDocument(); + }); + }); + + it('gracefully falls back to mock data on Horizon fetch error', async () => { + const { container } = render(); + await waitFor(() => { + expect(container.querySelector('[aria-rowcount="6"]')).toBeInTheDocument(); + }); + }); + + it('does not show error alert on fetch fallback', async () => { + const { container } = render(); + await waitFor(() => { + expect(container.querySelector('[aria-rowcount="6"]')).toBeInTheDocument(); + }); + expect(screen.queryByRole('alert')).not.toBeInTheDocument(); + }); + + it('uses correct Horizon URL for mainnet', async () => { + mockFetch.mockResolvedValue({ + ok: true, + json: () => Promise.resolve(mockHorizonResponse()), + }); + const mock = useWalletHook.useWallet as ReturnType; + mock.mockReturnValue({ activeAddress: 'GABC1234567890', network: 'MAINNET' }); + render(); + await waitFor(() => { + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining('horizon.stellar.org'), + ); + }); + }); +}); + +describe('TransactionHistory — custom address prop', () => { + it('uses provided address prop instead of wallet address', async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve(mockHorizonResponse()), + }); + const originalFetch = global.fetch; + global.fetch = mockFetch; + + render(); + await waitFor(() => { + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining('GCUSTOM1234567890'), + ); + }); + + global.fetch = originalFetch; + }); +}); + +describe('TransactionHistory — contractId filter', () => { + it('passes contractId to Horizon URL', async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve(mockHorizonResponse()), + }); + const originalFetch = global.fetch; + global.fetch = mockFetch; + + const mock = useWalletHook.useWallet as ReturnType; + mock.mockReturnValue({ activeAddress: 'GABC1234567890', network: 'TESTNET' }); + + render(); + await waitFor(() => { + expect(mockFetch).toHaveBeenCalled(); + }); + + global.fetch = originalFetch; + }); +}); + +describe('TransactionHistory — pageSize', () => { + it('uses provided pageSize', () => { + render(); + expect(screen.getByText('Transaction History')).toBeInTheDocument(); + }); +}); + +describe('TransactionHistory — custom network prop', () => { + it('uses FUTURENET Horizon URL', async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve(mockHorizonResponse()), + }); + const originalFetch = global.fetch; + global.fetch = mockFetch; + + const mock = useWalletHook.useWallet as ReturnType; + mock.mockReturnValue({ activeAddress: 'GABC1234567890', network: 'FUTURENET' }); + + render(); + await waitFor(() => { + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining('horizon-futurenet.stellar.org'), + ); + }); + + global.fetch = originalFetch; + }); +}); diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index 824c9f99..2228691b 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -24,5 +24,14 @@ export default defineConfig({ globals: true, environment: 'jsdom', setupFiles: './src/test/setup.ts', + coverage: { + include: ['src/components/GroupCard.tsx', 'src/components/ContributionFlow.tsx', 'src/components/TransactionHistory.tsx'], + thresholds: { + lines: 80, + functions: 80, + branches: 70, + statements: 80, + }, + }, }, });