Implemented Currency Selector # 34 #46
120
app/index.tsx
120
app/index.tsx
@ -1,13 +1,25 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { ScrollView, Text, Alert, Button } from "react-native";
|
import {
|
||||||
|
ScrollView,
|
||||||
|
Text,
|
||||||
|
Alert,
|
||||||
|
Button,
|
||||||
|
View,
|
||||||
|
StyleSheet,
|
||||||
|
} from "react-native";
|
||||||
import PlayerSelector from "@/components/PlayerSelector";
|
import PlayerSelector from "@/components/PlayerSelector";
|
||||||
import BuyInSelector from "@/components/BuyInSelector";
|
import BuyInSelector from "@/components/BuyInSelector";
|
||||||
import ChipsSelector from "@/components/ChipsSelector";
|
import ChipsSelector from "@/components/ChipsSelector";
|
||||||
import ChipDistributionSummary from "@/components/ChipDistributionSummary";
|
import ChipDistributionSummary from "@/components/ChipDistributionSummary";
|
||||||
import ChipDetection from "@/components/ChipDetection";
|
import ChipDetection from "@/components/ChipDetection";
|
||||||
|
import CurrencySelector from "@/components/CurrencySelector";
|
||||||
import { saveState, loadState } from "@/components/CalculatorState";
|
import { saveState, loadState } from "@/components/CalculatorState";
|
||||||
import { savePersistentState, loadPersistentState } from "@/components/PersistentState";
|
import {
|
||||||
|
savePersistentState,
|
||||||
|
loadPersistentState,
|
||||||
|
} from "@/components/PersistentState";
|
||||||
|
|
||||||
|
// Your existing states
|
||||||
export enum COLORS {
|
export enum COLORS {
|
||||||
"white",
|
"white",
|
||||||
"red",
|
"red",
|
||||||
@ -21,6 +33,8 @@ const IndexScreen: React.FC = () => {
|
|||||||
const [buyInAmount, setBuyInAmount] = useState<number>(20);
|
const [buyInAmount, setBuyInAmount] = useState<number>(20);
|
||||||
const [numberOfChips, setNumberOfChips] = useState<number>(5);
|
const [numberOfChips, setNumberOfChips] = useState<number>(5);
|
||||||
const [totalChipsCount, setTotalChipsCount] = useState<number[]>([]);
|
const [totalChipsCount, setTotalChipsCount] = useState<number[]>([]);
|
||||||
|
const [selectedCurrency, setSelectedCurrency] = useState<string>("$");
|
||||||
|
const [isSettingsVisible, setIsSettingsVisible] = useState(false);
|
||||||
|
|
||||||
// Load persistent data on startup
|
// Load persistent data on startup
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -30,12 +44,13 @@ const IndexScreen: React.FC = () => {
|
|||||||
const savedState = await loadPersistentState();
|
const savedState = await loadPersistentState();
|
||||||
if (savedState) {
|
if (savedState) {
|
||||||
console.log("Persistent state restored:", savedState);
|
console.log("Persistent state restored:", savedState);
|
||||||
setPlayerCount(savedState.playerCount);
|
setPlayerCount(savedState.playerCount || 2); // Use defaults if missing
|
||||||
setBuyInAmount(savedState.buyInAmount);
|
setBuyInAmount(savedState.buyInAmount || 20); // Use defaults if missing
|
||||||
setNumberOfChips(savedState.numberOfChips);
|
setNumberOfChips(savedState.numberOfChips || 5); // Use defaults if missing
|
||||||
setTotalChipsCount(savedState.totalChipsCount);
|
setTotalChipsCount(savedState.totalChipsCount || []); // Use defaults if missing
|
||||||
|
setSelectedCurrency(savedState.selectedCurrency || "$"); // Restore the selected currency, defaulting to "$" if not available
|
||||||
} else {
|
} else {
|
||||||
console.log("No persistent state found.");
|
console.log("No persistent state found, using defaults.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading persistent state:", error);
|
console.error("Error loading persistent state:", error);
|
||||||
@ -44,14 +59,18 @@ const IndexScreen: React.FC = () => {
|
|||||||
loadPersistentData();
|
loadPersistentData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Save game state to selected slot
|
|
||||||
const handleSave = async (slot: "SLOT1" | "SLOT2") => {
|
const handleSave = async (slot: "SLOT1" | "SLOT2") => {
|
||||||
if (buyInAmount === null) {
|
if (buyInAmount === null) {
|
||||||
Alert.alert("Error", "Please select a valid buy-in amount");
|
Alert.alert("Error", "Please select a valid buy-in amount");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const state = { playerCount, buyInAmount, numberOfChips, totalChipsCount };
|
const state = {
|
||||||
|
playerCount,
|
||||||
|
buyInAmount,
|
||||||
|
numberOfChips,
|
||||||
|
totalChipsCount,
|
||||||
|
selectedCurrency,
|
||||||
|
};
|
||||||
try {
|
try {
|
||||||
await saveState(slot, state);
|
await saveState(slot, state);
|
||||||
await savePersistentState(state);
|
await savePersistentState(state);
|
||||||
@ -63,7 +82,6 @@ const IndexScreen: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load game state from selected slot
|
|
||||||
const handleLoad = async (slot: "SLOT1" | "SLOT2") => {
|
const handleLoad = async (slot: "SLOT1" | "SLOT2") => {
|
||||||
try {
|
try {
|
||||||
const loadedState = await loadState(slot);
|
const loadedState = await loadState(slot);
|
||||||
@ -72,6 +90,7 @@ const IndexScreen: React.FC = () => {
|
|||||||
setBuyInAmount(loadedState.buyInAmount);
|
setBuyInAmount(loadedState.buyInAmount);
|
||||||
setNumberOfChips(loadedState.numberOfChips);
|
setNumberOfChips(loadedState.numberOfChips);
|
||||||
setTotalChipsCount(loadedState.totalChipsCount);
|
setTotalChipsCount(loadedState.totalChipsCount);
|
||||||
|
setSelectedCurrency(loadedState.selectedCurrency || "$");
|
||||||
await savePersistentState(loadedState);
|
await savePersistentState(loadedState);
|
||||||
console.log(`Game state loaded from ${slot}:`, loadedState);
|
console.log(`Game state loaded from ${slot}:`, loadedState);
|
||||||
Alert.alert("Success", `State loaded from ${slot}`);
|
Alert.alert("Success", `State loaded from ${slot}`);
|
||||||
@ -86,29 +105,92 @@ const IndexScreen: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView contentContainerStyle={{ padding: 20, flexGrow: 1 }}>
|
<ScrollView contentContainerStyle={{ padding: 20, flexGrow: 1 }}>
|
||||||
<PlayerSelector playerCount={playerCount} setPlayerCount={setPlayerCount} />
|
<View style={styles.header}>
|
||||||
<BuyInSelector setBuyInAmount={setBuyInAmount} />
|
<Text style={styles.headerTitle}>Poker Chips Helper</Text>
|
||||||
<ChipDetection updateChipCount={(chipData) => {
|
<Button
|
||||||
const chipCountArray = Object.values(chipData);
|
title="Settings"
|
||||||
setTotalChipsCount(chipCountArray);
|
onPress={() => setIsSettingsVisible(!isSettingsVisible)}
|
||||||
}} />
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{isSettingsVisible && (
|
||||||
|
<View style={styles.settingsContainer}>
|
||||||
|
<Text style={styles.settingTitle}>Currency Selector</Text>
|
||||||
|
<CurrencySelector
|
||||||
|
selectedCurrency={selectedCurrency}
|
||||||
|
setSelectedCurrency={setSelectedCurrency}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<PlayerSelector
|
||||||
|
playerCount={playerCount}
|
||||||
|
setPlayerCount={setPlayerCount}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BuyInSelector
|
||||||
|
selectedCurrency={selectedCurrency}
|
||||||
|
setBuyInAmount={setBuyInAmount}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ChipDetection
|
||||||
|
updateChipCount={(chipData) => {
|
||||||
|
const chipCountArray = Object.values(chipData);
|
||||||
|
setTotalChipsCount(chipCountArray);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<ChipsSelector
|
<ChipsSelector
|
||||||
totalChipsCount={totalChipsCount}
|
totalChipsCount={totalChipsCount}
|
||||||
setTotalChipsCount={setTotalChipsCount}
|
setTotalChipsCount={setTotalChipsCount}
|
||||||
numberOfChips={numberOfChips}
|
numberOfChips={numberOfChips}
|
||||||
setNumberOfChips={setNumberOfChips}
|
setNumberOfChips={setNumberOfChips}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ChipDistributionSummary
|
<ChipDistributionSummary
|
||||||
playerCount={playerCount}
|
playerCount={playerCount}
|
||||||
buyInAmount={buyInAmount}
|
buyInAmount={buyInAmount}
|
||||||
totalChipsCount={totalChipsCount}
|
totalChipsCount={totalChipsCount}
|
||||||
|
selectedCurrency={selectedCurrency}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
title="Save to Slot 1"
|
||||||
|
onPress={() => handleSave("SLOT1")}
|
||||||
|
disabled={buyInAmount === null}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
title="Save to Slot 2"
|
||||||
|
onPress={() => handleSave("SLOT2")}
|
||||||
|
disabled={buyInAmount === null}
|
||||||
/>
|
/>
|
||||||
<Button title="Save to Slot 1" onPress={() => handleSave("SLOT1")} disabled={buyInAmount === null} />
|
|
||||||
<Button title="Save to Slot 2" onPress={() => handleSave("SLOT2")} disabled={buyInAmount === null} />
|
|
||||||
<Button title="Load from Slot 1" onPress={() => handleLoad("SLOT1")} />
|
<Button title="Load from Slot 1" onPress={() => handleLoad("SLOT1")} />
|
||||||
<Button title="Load from Slot 2" onPress={() => handleLoad("SLOT2")} />
|
<Button title="Load from Slot 2" onPress={() => handleLoad("SLOT2")} />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
header: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
settingsContainer: {
|
||||||
|
marginBottom: 20,
|
||||||
|
padding: 10,
|
||||||
|
backgroundColor: "#f5f5f5",
|
||||||
|
borderRadius: 5,
|
||||||
|
},
|
||||||
|
settingTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: "bold",
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default IndexScreen;
|
export default IndexScreen;
|
@ -10,11 +10,15 @@ import { MaterialIcons } from "@expo/vector-icons";
|
|||||||
|
|
||||||
interface BuyInSelectorProps {
|
interface BuyInSelectorProps {
|
||||||
setBuyInAmount: React.Dispatch<React.SetStateAction<number>>;
|
setBuyInAmount: React.Dispatch<React.SetStateAction<number>>;
|
||||||
|
selectedCurrency: string; // Accept selectedCurrency as a prop
|
||||||
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultBuyInOptions = [10, 25, 50];
|
const defaultBuyInOptions = [10, 25, 50];
|
||||||
|
|
||||||
const BuyInSelector: React.FC<BuyInSelectorProps> = ({ setBuyInAmount }) => {
|
const BuyInSelector: React.FC<BuyInSelectorProps> = ({
|
||||||
|
setBuyInAmount,
|
||||||
|
selectedCurrency,
|
||||||
|
}) => {
|
||||||
const [customAmount, setCustomAmount] = useState("");
|
const [customAmount, setCustomAmount] = useState("");
|
||||||
const [buyInAmount, setBuyInAmountState] = useState<number | null>(null);
|
const [buyInAmount, setBuyInAmountState] = useState<number | null>(null);
|
||||||
|
|
||||||
@ -54,7 +58,10 @@ const BuyInSelector: React.FC<BuyInSelectorProps> = ({ setBuyInAmount }) => {
|
|||||||
]}
|
]}
|
||||||
onPress={() => handleBuyInSelection(amount)}
|
onPress={() => handleBuyInSelection(amount)}
|
||||||
>
|
>
|
||||||
<Text style={styles.buttonText}>{amount}</Text>
|
<Text style={styles.buttonText}>
|
||||||
|
{selectedCurrency} {amount}{" "}
|
||||||
|
{/* Display the selected currency before the amount */}
|
||||||
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
@ -69,7 +76,9 @@ const BuyInSelector: React.FC<BuyInSelectorProps> = ({ setBuyInAmount }) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Text style={styles.selectionText}>
|
<Text style={styles.selectionText}>
|
||||||
Selected Buy-in: {buyInAmount !== null ? buyInAmount : "None"}
|
Selected Buy-in:{" "}
|
||||||
|
{buyInAmount !== null ? `${selectedCurrency} ${buyInAmount}` : "None"}{" "}
|
||||||
|
{/* Display the currency here */}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
@ -10,9 +10,13 @@ export interface PokerState {
|
|||||||
buyInAmount: number | null;
|
buyInAmount: number | null;
|
||||||
numberOfChips: number;
|
numberOfChips: number;
|
||||||
totalChipsCount: number[];
|
totalChipsCount: number[];
|
||||||
|
selectedCurrency: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const saveState = async (slot: keyof typeof STORAGE_KEYS, state: PokerState) => {
|
export const saveState = async (
|
||||||
|
slot: keyof typeof STORAGE_KEYS,
|
||||||
|
state: PokerState
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
await AsyncStorage.setItem(STORAGE_KEYS[slot], JSON.stringify(state));
|
await AsyncStorage.setItem(STORAGE_KEYS[slot], JSON.stringify(state));
|
||||||
return { success: true, message: `State saved to ${slot}` };
|
return { success: true, message: `State saved to ${slot}` };
|
||||||
@ -21,7 +25,9 @@ export const saveState = async (slot: keyof typeof STORAGE_KEYS, state: PokerSta
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadState = async (slot: keyof typeof STORAGE_KEYS): Promise<PokerState | null> => {
|
export const loadState = async (
|
||||||
|
slot: keyof typeof STORAGE_KEYS
|
||||||
|
): Promise<PokerState | null> => {
|
||||||
try {
|
try {
|
||||||
const storedState = await AsyncStorage.getItem(STORAGE_KEYS[slot]);
|
const storedState = await AsyncStorage.getItem(STORAGE_KEYS[slot]);
|
||||||
return storedState ? JSON.parse(storedState) : null;
|
return storedState ? JSON.parse(storedState) : null;
|
||||||
|
@ -7,6 +7,7 @@ interface ChipDistributionSummaryProps {
|
|||||||
buyInAmount: number;
|
buyInAmount: number;
|
||||||
totalChipsCount: number[];
|
totalChipsCount: number[];
|
||||||
colors?: ColorValue[];
|
colors?: ColorValue[];
|
||||||
|
selectedCurrency: string; // Add the selectedCurrency as a prop here
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChipDistributionSummary = ({
|
const ChipDistributionSummary = ({
|
||||||
@ -14,6 +15,7 @@ const ChipDistributionSummary = ({
|
|||||||
buyInAmount,
|
buyInAmount,
|
||||||
totalChipsCount,
|
totalChipsCount,
|
||||||
colors = ["white", "red", "green", "blue", "black"],
|
colors = ["white", "red", "green", "blue", "black"],
|
||||||
|
selectedCurrency,
|
||||||
}: ChipDistributionSummaryProps) => {
|
}: ChipDistributionSummaryProps) => {
|
||||||
const validDenominations: validDenomination[] = [
|
const validDenominations: validDenomination[] = [
|
||||||
0.05, 0.1, 0.25, 0.5, 1, 2, 2.5, 5, 10, 20, 50, 100,
|
0.05, 0.1, 0.25, 0.5, 1, 2, 2.5, 5, 10, 20, 50, 100,
|
||||||
@ -35,7 +37,6 @@ const ChipDistributionSummary = ({
|
|||||||
| 50
|
| 50
|
||||||
| 100;
|
| 100;
|
||||||
|
|
||||||
// Return the closest (but lower) valid denomination to the target
|
|
||||||
const findFloorDenomination = (target: number): validDenomination => {
|
const findFloorDenomination = (target: number): validDenomination => {
|
||||||
let current: validDenomination = validDenominations[0];
|
let current: validDenomination = validDenominations[0];
|
||||||
validDenominations.forEach((value, index) => {
|
validDenominations.forEach((value, index) => {
|
||||||
@ -44,8 +45,6 @@ const ChipDistributionSummary = ({
|
|||||||
return current;
|
return current;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bound for the value of the highest chip
|
|
||||||
// This is somewhat arbitray, but 1/3 to 1/4 is reasonable depending on the number of colors.
|
|
||||||
const maxDenomination = useMemo(() => {
|
const maxDenomination = useMemo(() => {
|
||||||
if (totalChipsCount.length > 3) {
|
if (totalChipsCount.length > 3) {
|
||||||
return findFloorDenomination(buyInAmount / 3);
|
return findFloorDenomination(buyInAmount / 3);
|
||||||
@ -54,13 +53,11 @@ const ChipDistributionSummary = ({
|
|||||||
}
|
}
|
||||||
}, [totalChipsCount]);
|
}, [totalChipsCount]);
|
||||||
|
|
||||||
// Total value of the pot
|
|
||||||
const potValue = useMemo(
|
const potValue = useMemo(
|
||||||
() => buyInAmount * playerCount,
|
() => buyInAmount * playerCount,
|
||||||
[buyInAmount, playerCount]
|
[buyInAmount, playerCount]
|
||||||
);
|
);
|
||||||
|
|
||||||
// The total value of all chips distributed to a single player. Ideally should be equal to buyInAmount
|
|
||||||
const totalValue = useMemo(() => {
|
const totalValue = useMemo(() => {
|
||||||
let value = 0;
|
let value = 0;
|
||||||
for (let i = 0; i < totalChipsCount.length; i++) {
|
for (let i = 0; i < totalChipsCount.length; i++) {
|
||||||
@ -69,14 +66,11 @@ const ChipDistributionSummary = ({
|
|||||||
return value;
|
return value;
|
||||||
}, [distributions, denominations]);
|
}, [distributions, denominations]);
|
||||||
|
|
||||||
// Maximum quantity of each chip color which may be distributed to a single player before running out
|
|
||||||
const maxPossibleDistribution = useMemo(
|
const maxPossibleDistribution = useMemo(
|
||||||
() => totalChipsCount.map((v) => Math.floor(v / playerCount)),
|
() => totalChipsCount.map((v) => Math.floor(v / playerCount)),
|
||||||
[totalChipsCount, playerCount]
|
[totalChipsCount, playerCount]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Redenominate the chips in case of failure to properly distribute.
|
|
||||||
// Move the shuffle index to the next lowest denomination, and keep all else same
|
|
||||||
const redenominate = useCallback(
|
const redenominate = useCallback(
|
||||||
(
|
(
|
||||||
invalidDenomination: validDenomination[],
|
invalidDenomination: validDenomination[],
|
||||||
@ -106,17 +100,14 @@ const ChipDistributionSummary = ({
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Dynamically set denominations and distributions from changing inputs
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let testDenomination: validDenomination[] = [];
|
let testDenomination: validDenomination[] = [];
|
||||||
|
|
||||||
const numColors = totalChipsCount.length;
|
const numColors = totalChipsCount.length;
|
||||||
const testDistribution: number[] = [];
|
const testDistribution: number[] = [];
|
||||||
for (let i = 0; i < numColors; ++i) {
|
for (let i = 0; i < numColors; ++i) {
|
||||||
testDistribution.push(0);
|
testDistribution.push(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start with max denominations, then push on the next adjacent lower denomination
|
|
||||||
testDenomination.push(maxDenomination);
|
testDenomination.push(maxDenomination);
|
||||||
let currentDenominationIndex: number =
|
let currentDenominationIndex: number =
|
||||||
validDenominations.indexOf(maxDenomination);
|
validDenominations.indexOf(maxDenomination);
|
||||||
@ -127,9 +118,6 @@ const ChipDistributionSummary = ({
|
|||||||
}
|
}
|
||||||
testDenomination.reverse();
|
testDenomination.reverse();
|
||||||
|
|
||||||
// Distribute the chips using the test denomination
|
|
||||||
// If distribution fails to equal the buy-in, redenominate and re-try
|
|
||||||
// Algorithm could be improved with more complexity and optimization
|
|
||||||
let remainingValue = buyInAmount;
|
let remainingValue = buyInAmount;
|
||||||
let fail = true;
|
let fail = true;
|
||||||
let failCount = 0;
|
let failCount = 0;
|
||||||
@ -167,7 +155,9 @@ const ChipDistributionSummary = ({
|
|||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Text style={styles.title}>Distribution & Denomination</Text>
|
<Text style={styles.title}>Distribution & Denomination</Text>
|
||||||
<Text style={styles.subTitle}>${potValue} Pot</Text>
|
<Text style={styles.subTitle}>
|
||||||
|
{selectedCurrency} {potValue} Pot
|
||||||
|
</Text>
|
||||||
<View style={styles.chipContainer}>
|
<View style={styles.chipContainer}>
|
||||||
{totalChipsCount.map((_, index) => (
|
{totalChipsCount.map((_, index) => (
|
||||||
<View style={styles.chipRow} key={index}>
|
<View style={styles.chipRow} key={index}>
|
||||||
@ -187,12 +177,14 @@ const ChipDistributionSummary = ({
|
|||||||
...(colors[index] === "white" && styles.whiteShadow),
|
...(colors[index] === "white" && styles.whiteShadow),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
${denominations[index]} each
|
{selectedCurrency} {denominations[index]} each
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.chipText}>Total Value:{totalValue}</Text>
|
<Text style={styles.chipText}>
|
||||||
|
Total Value: {selectedCurrency} {totalValue}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
46
components/CurrencySelector.tsx
Normal file
46
components/CurrencySelector.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { View, StyleSheet, Text } from "react-native";
|
||||||
|
import { Picker } from "@react-native-picker/picker";
|
||||||
|
|
||||||
|
interface CurrencySelectorProps {
|
||||||
|
selectedCurrency: string;
|
||||||
|
setSelectedCurrency: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CurrencySelector: React.FC<CurrencySelectorProps> = ({
|
||||||
|
selectedCurrency,
|
||||||
|
setSelectedCurrency,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text style={styles.title}>Select Currency:</Text>
|
||||||
|
<Picker
|
||||||
|
selectedValue={selectedCurrency}
|
||||||
|
onValueChange={(itemValue) => setSelectedCurrency(itemValue)}
|
||||||
|
style={styles.picker}
|
||||||
|
testID="currency-picker" // ✅ Add testID here
|
||||||
|
>
|
||||||
|
<Picker.Item label="USD ($)" value="$" />
|
||||||
![]() One thing about currencies is that multiple currencies sometimes use the same symbol (example, Mexican Peso also uses the One thing about currencies is that multiple currencies sometimes use the same symbol (example, Mexican Peso also uses the `$` sign).
I think what you have is good for our app though and you do not need to change anything. This is just an interesting fact.
![]() Yes, good to know! I think we're good with the current approach, as you said Yes, good to know! I think we're good with the current approach, as you said
|
|||||||
|
<Picker.Item label="Euro (€)" value="€" />
|
||||||
|
<Picker.Item label="Pound (£)" value="£" />
|
||||||
|
<Picker.Item label="INR (₹)" value="₹" />
|
||||||
|
</Picker>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 18,
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
picker: {
|
||||||
|
height: 50,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default CurrencySelector;
|
@ -7,8 +7,18 @@ export interface PokerState {
|
|||||||
buyInAmount: number | null;
|
buyInAmount: number | null;
|
||||||
numberOfChips: number;
|
numberOfChips: number;
|
||||||
totalChipsCount: number[];
|
totalChipsCount: number[];
|
||||||
|
selectedCurrency: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_STATE: PokerState = {
|
||||||
|
playerCount: 0,
|
||||||
|
buyInAmount: null,
|
||||||
|
numberOfChips: 0,
|
||||||
|
totalChipsCount: [],
|
||||||
|
selectedCurrency: "$",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 🔹 Save state with currency
|
||||||
export const savePersistentState = async (state: PokerState) => {
|
export const savePersistentState = async (state: PokerState) => {
|
||||||
try {
|
try {
|
||||||
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
||||||
@ -18,11 +28,12 @@ export const savePersistentState = async (state: PokerState) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadPersistentState = async (): Promise<PokerState | null> => {
|
// 🔹 Load state with currency
|
||||||
|
export const loadPersistentState = async (): Promise<PokerState> => {
|
||||||
try {
|
try {
|
||||||
const storedState = await AsyncStorage.getItem(STORAGE_KEY);
|
const storedState = await AsyncStorage.getItem(STORAGE_KEY);
|
||||||
return storedState ? JSON.parse(storedState) : null;
|
return storedState ? JSON.parse(storedState) : DEFAULT_STATE; // Ensure default values
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return null;
|
return DEFAULT_STATE;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -14,28 +14,34 @@ describe("BuyInSelector Component", () => {
|
|||||||
let getByText;
|
let getByText;
|
||||||
let getByPlaceholderText;
|
let getByPlaceholderText;
|
||||||
|
|
||||||
const renderComponent = () => {
|
// Render the component with the necessary props
|
||||||
const result = render(<BuyInSelector setBuyInAmount={setBuyInAmount} />);
|
const renderComponent = (selectedCurrency = "$") => {
|
||||||
|
const result = render(
|
||||||
|
<BuyInSelector
|
||||||
|
setBuyInAmount={setBuyInAmount}
|
||||||
|
selectedCurrency={selectedCurrency}
|
||||||
|
/>
|
||||||
|
);
|
||||||
getByText = result.getByText;
|
getByText = result.getByText;
|
||||||
getByPlaceholderText = result.getByPlaceholderText;
|
getByPlaceholderText = result.getByPlaceholderText;
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setBuyInAmount = jest.fn();
|
setBuyInAmount = jest.fn();
|
||||||
renderComponent();
|
renderComponent(); // Render with default currency
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders the buy-in options and input correctly", () => {
|
it("renders the buy-in options and input correctly", () => {
|
||||||
expect(getByText("Select Buy-in Amount:")).toBeTruthy();
|
expect(getByText("Select Buy-in Amount:")).toBeTruthy();
|
||||||
expect(getByText("10")).toBeTruthy();
|
expect(getByText("$ 10")).toBeTruthy();
|
||||||
expect(getByText("25")).toBeTruthy();
|
expect(getByText("$ 25")).toBeTruthy();
|
||||||
expect(getByText("50")).toBeTruthy();
|
expect(getByText("$ 50")).toBeTruthy();
|
||||||
expect(getByPlaceholderText("Enter custom buy-in")).toBeTruthy();
|
expect(getByPlaceholderText("Enter custom buy-in")).toBeTruthy();
|
||||||
expect(getByText("Selected Buy-in: None")).toBeTruthy(); // Check default selection
|
expect(getByText("Selected Buy-in: None")).toBeTruthy(); // Check default selection
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets a predefined buy-in amount correctly", () => {
|
it("sets a predefined buy-in amount correctly", () => {
|
||||||
fireEvent.press(getByText("25"));
|
fireEvent.press(getByText("$ 25"));
|
||||||
expect(setBuyInAmount).toHaveBeenCalledWith(25);
|
expect(setBuyInAmount).toHaveBeenCalledWith(25);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -53,7 +59,7 @@ describe("BuyInSelector Component", () => {
|
|||||||
|
|
||||||
it("clears the custom amount when selecting a predefined option", () => {
|
it("clears the custom amount when selecting a predefined option", () => {
|
||||||
fireEvent.changeText(getByPlaceholderText("Enter custom buy-in"), "100");
|
fireEvent.changeText(getByPlaceholderText("Enter custom buy-in"), "100");
|
||||||
fireEvent.press(getByText("50"));
|
fireEvent.press(getByText("$ 50"));
|
||||||
expect(setBuyInAmount).toHaveBeenCalledWith(50);
|
expect(setBuyInAmount).toHaveBeenCalledWith(50);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -69,8 +75,8 @@ describe("BuyInSelector Component", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("triggers state update every time a buy-in option is clicked, even if it's the same", () => {
|
it("triggers state update every time a buy-in option is clicked, even if it's the same", () => {
|
||||||
fireEvent.press(getByText("25")); // First click
|
fireEvent.press(getByText("$ 25")); // First click
|
||||||
fireEvent.press(getByText("25")); // Clicking the same option again
|
fireEvent.press(getByText("$ 25")); // Clicking the same option again
|
||||||
expect(setBuyInAmount).toHaveBeenCalledTimes(2); // Expect it to be called twice
|
expect(setBuyInAmount).toHaveBeenCalledTimes(2); // Expect it to be called twice
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -85,7 +91,15 @@ describe("BuyInSelector Component", () => {
|
|||||||
it("updates state correctly when selecting predefined buy-in after entering a custom amount", () => {
|
it("updates state correctly when selecting predefined buy-in after entering a custom amount", () => {
|
||||||
fireEvent.changeText(getByPlaceholderText("Enter custom buy-in"), "200");
|
fireEvent.changeText(getByPlaceholderText("Enter custom buy-in"), "200");
|
||||||
expect(setBuyInAmount).toHaveBeenCalledWith(200);
|
expect(setBuyInAmount).toHaveBeenCalledWith(200);
|
||||||
fireEvent.press(getByText("10"));
|
fireEvent.press(getByText("$ 10"));
|
||||||
expect(setBuyInAmount).toHaveBeenCalledWith(10);
|
expect(setBuyInAmount).toHaveBeenCalledWith(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("displays selected currency correctly", () => {
|
||||||
|
renderComponent("€"); // Test with a different currency
|
||||||
|
expect(getByText("€ 10")).toBeTruthy();
|
||||||
|
expect(getByText("€ 25")).toBeTruthy();
|
||||||
|
expect(getByText("€ 50")).toBeTruthy();
|
||||||
|
expect(getByText("Selected Buy-in: None")).toBeTruthy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
import { saveState, loadState, PokerState } from "../CalculatorState";
|
import { saveState, loadState, PokerState } from "../CalculatorState";
|
||||||
|
|
||||||
|
// Mock AsyncStorage
|
||||||
jest.mock("@react-native-async-storage/async-storage", () =>
|
jest.mock("@react-native-async-storage/async-storage", () =>
|
||||||
require("@react-native-async-storage/async-storage/jest/async-storage-mock")
|
require("@react-native-async-storage/async-storage/jest/async-storage-mock")
|
||||||
);
|
);
|
||||||
@ -11,13 +12,14 @@ describe("CalculatorState.tsx", () => {
|
|||||||
buyInAmount: 50,
|
buyInAmount: 50,
|
||||||
numberOfChips: 5,
|
numberOfChips: 5,
|
||||||
totalChipsCount: [100, 200, 150, 50, 75],
|
totalChipsCount: [100, 200, 150, 50, 75],
|
||||||
|
selectedCurrency: "$", // Including selectedCurrency in mockState
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should save state successfully", async () => {
|
it("should save state successfully to SLOT1", async () => {
|
||||||
await saveState("SLOT1", mockState);
|
await saveState("SLOT1", mockState);
|
||||||
expect(AsyncStorage.setItem).toHaveBeenCalledWith(
|
expect(AsyncStorage.setItem).toHaveBeenCalledWith(
|
||||||
"@poker_state_slot1",
|
"@poker_state_slot1",
|
||||||
@ -25,28 +27,60 @@ describe("CalculatorState.tsx", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should save state successfully to SLOT2", async () => {
|
||||||
|
await saveState("SLOT2", mockState);
|
||||||
|
expect(AsyncStorage.setItem).toHaveBeenCalledWith(
|
||||||
|
"@poker_state_slot2",
|
||||||
|
JSON.stringify(mockState)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("should fail to save state if an error occurs", async () => {
|
it("should fail to save state if an error occurs", async () => {
|
||||||
jest.spyOn(AsyncStorage, "setItem").mockRejectedValueOnce(new Error("Failed to save"));
|
jest
|
||||||
|
.spyOn(AsyncStorage, "setItem")
|
||||||
|
.mockRejectedValueOnce(new Error("Failed to save"));
|
||||||
const result = await saveState("SLOT1", mockState);
|
const result = await saveState("SLOT1", mockState);
|
||||||
expect(result.success).toBe(false);
|
expect(result.success).toBe(false);
|
||||||
expect(result.message).toBe("Failed to save state");
|
expect(result.message).toBe("Failed to save state");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should load state successfully", async () => {
|
it("should load state successfully from SLOT1", async () => {
|
||||||
await AsyncStorage.setItem("@poker_state_slot1", JSON.stringify(mockState));
|
await AsyncStorage.setItem("@poker_state_slot1", JSON.stringify(mockState));
|
||||||
const result = await loadState("SLOT1");
|
const result = await loadState("SLOT1");
|
||||||
expect(result).toEqual(mockState);
|
expect(result).toEqual(mockState);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return null if no state is found", async () => {
|
it("should load state successfully from SLOT2", async () => {
|
||||||
|
await AsyncStorage.setItem("@poker_state_slot2", JSON.stringify(mockState));
|
||||||
|
const result = await loadState("SLOT2");
|
||||||
|
expect(result).toEqual(mockState);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return null if no state is found in SLOT1", async () => {
|
||||||
jest.spyOn(AsyncStorage, "getItem").mockResolvedValueOnce(null);
|
jest.spyOn(AsyncStorage, "getItem").mockResolvedValueOnce(null);
|
||||||
const result = await loadState("SLOT1");
|
const result = await loadState("SLOT1");
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return null if an error occurs while loading state", async () => {
|
it("should return null if no state is found in SLOT2", async () => {
|
||||||
jest.spyOn(AsyncStorage, "getItem").mockRejectedValueOnce(new Error("Failed to load"));
|
jest.spyOn(AsyncStorage, "getItem").mockResolvedValueOnce(null);
|
||||||
|
const result = await loadState("SLOT2");
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return null if an error occurs while loading state from SLOT1", async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(AsyncStorage, "getItem")
|
||||||
|
.mockRejectedValueOnce(new Error("Failed to load"));
|
||||||
const result = await loadState("SLOT1");
|
const result = await loadState("SLOT1");
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should return null if an error occurs while loading state from SLOT2", async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(AsyncStorage, "getItem")
|
||||||
|
.mockRejectedValueOnce(new Error("Failed to load"));
|
||||||
|
const result = await loadState("SLOT2");
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -23,14 +23,18 @@ describe("ChipDistributionSummary Component", () => {
|
|||||||
expect(getByText("Distribution & Denomination")).toBeTruthy();
|
expect(getByText("Distribution & Denomination")).toBeTruthy();
|
||||||
|
|
||||||
expectedDistribution.forEach((count, index) => {
|
expectedDistribution.forEach((count, index) => {
|
||||||
expect(getAllByText(new RegExp(`${count} chips:`, "i"))).toBeTruthy();
|
// Ensure "X chips:" appears correctly
|
||||||
|
expect(getAllByText(new RegExp(`^${count}\\s+chips:`, "i"))).toBeTruthy();
|
||||||
|
|
||||||
|
// Ensure value format matches the rendered output
|
||||||
expect(
|
expect(
|
||||||
getByText(new RegExp(`\\$${expectedDenominations[index]} each`, "i"))
|
getByText(
|
||||||
|
new RegExp(`^\\s*${expectedDenominations[index]}\\s+each$`, "i")
|
||||||
|
)
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Case not currently supported
|
|
||||||
test.skip("renders fallback message when no valid distribution", () => {
|
test.skip("renders fallback message when no valid distribution", () => {
|
||||||
const { getByText } = render(
|
const { getByText } = render(
|
||||||
<ChipDistributionSummary
|
<ChipDistributionSummary
|
||||||
@ -42,7 +46,6 @@ describe("ChipDistributionSummary Component", () => {
|
|||||||
expect(getByText("No valid distribution calculated yet.")).toBeTruthy();
|
expect(getByText("No valid distribution calculated yet.")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Case not currently supported
|
|
||||||
test.skip("scales down chips if exceeding MAX_CHIPS", () => {
|
test.skip("scales down chips if exceeding MAX_CHIPS", () => {
|
||||||
const playerCount = 2;
|
const playerCount = 2;
|
||||||
let totalChipsCount = [300, 400, 500, 600, 700];
|
let totalChipsCount = [300, 400, 500, 600, 700];
|
||||||
@ -56,7 +59,7 @@ describe("ChipDistributionSummary Component", () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const expectedDistribution = [30, 40, 50, 60, 70]; // Adjust to match actual component calculations
|
const expectedDistribution = [30, 40, 50, 60, 70]; // Adjust as per actual component calculations
|
||||||
|
|
||||||
const { getByText } = render(
|
const { getByText } = render(
|
||||||
<ChipDistributionSummary
|
<ChipDistributionSummary
|
||||||
@ -69,8 +72,7 @@ describe("ChipDistributionSummary Component", () => {
|
|||||||
expect(getByText("Distribution & Denomination")).toBeTruthy();
|
expect(getByText("Distribution & Denomination")).toBeTruthy();
|
||||||
|
|
||||||
expectedDistribution.forEach((count, index) => {
|
expectedDistribution.forEach((count, index) => {
|
||||||
expect(getByText(new RegExp(`${count} chips:`, "i"))).toBeTruthy();
|
expect(getByText(new RegExp(`^${count}\\s+chips:`, "i"))).toBeTruthy();
|
||||||
// expect(getByText(new RegExp(`$${count} each`, "i"))).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
49
components/__tests__/CurrencySelector.test.tsx
Normal file
49
components/__tests__/CurrencySelector.test.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { render, fireEvent } from "@testing-library/react-native";
|
||||||
|
import CurrencySelector from "@/components/CurrencySelector";
|
||||||
|
|
||||||
|
describe("CurrencySelector Component", () => {
|
||||||
|
const mockSetSelectedCurrency = jest.fn();
|
||||||
|
|
||||||
|
test("renders CurrencySelector component correctly", () => {
|
||||||
|
const { getByText, getByTestId } = render(
|
||||||
|
<CurrencySelector
|
||||||
|
selectedCurrency="$"
|
||||||
|
setSelectedCurrency={mockSetSelectedCurrency}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getByText("Select Currency:")).toBeTruthy(); // Check label exists
|
||||||
|
expect(getByTestId("currency-picker")).toBeTruthy(); // Check Picker exists
|
||||||
|
});
|
||||||
|
|
||||||
|
test("calls setSelectedCurrency when a new currency is selected", () => {
|
||||||
|
const { getByTestId } = render(
|
||||||
|
<CurrencySelector
|
||||||
|
selectedCurrency="$"
|
||||||
|
setSelectedCurrency={mockSetSelectedCurrency}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const picker = getByTestId("currency-picker"); // Get Picker
|
||||||
|
|
||||||
|
fireEvent(picker, "onValueChange", "€"); // Simulate selecting Euro (€)
|
||||||
|
|
||||||
|
expect(mockSetSelectedCurrency).toHaveBeenCalledWith("€");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("updates selected currency when Picker value changes", () => {
|
||||||
|
const { getByTestId } = render(
|
||||||
|
<CurrencySelector
|
||||||
|
selectedCurrency="€"
|
||||||
|
setSelectedCurrency={mockSetSelectedCurrency}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const picker = getByTestId("currency-picker");
|
||||||
|
|
||||||
|
fireEvent(picker, "onValueChange", "$"); // Simulate selecting USD ($)
|
||||||
|
|
||||||
|
expect(mockSetSelectedCurrency).toHaveBeenCalledWith("$");
|
||||||
|
});
|
||||||
|
});
|
@ -1,17 +1,22 @@
|
|||||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
import { saveState, loadState, PokerState } from "../CalculatorState";
|
import {
|
||||||
|
savePersistentState,
|
||||||
|
loadPersistentState,
|
||||||
|
PokerState,
|
||||||
|
} from "../PersistentState";
|
||||||
|
|
||||||
jest.mock("@react-native-async-storage/async-storage", () => ({
|
jest.mock("@react-native-async-storage/async-storage", () => ({
|
||||||
setItem: jest.fn(),
|
setItem: jest.fn(),
|
||||||
getItem: jest.fn(),
|
getItem: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe("CalculatorState.tsx", () => {
|
describe("PersistentState.tsx", () => {
|
||||||
const mockState: PokerState = {
|
const mockState: PokerState = {
|
||||||
playerCount: 4,
|
playerCount: 4,
|
||||||
buyInAmount: 50,
|
buyInAmount: 50,
|
||||||
numberOfChips: 5,
|
numberOfChips: 5,
|
||||||
totalChipsCount: [100, 200, 150, 50, 75],
|
totalChipsCount: [100, 200, 150, 50, 75],
|
||||||
|
selectedCurrency: "$", // Including selectedCurrency in mockState
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -19,35 +24,78 @@ describe("CalculatorState.tsx", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should save state successfully", async () => {
|
it("should save state successfully", async () => {
|
||||||
|
// Mocking AsyncStorage.setItem to resolve successfully
|
||||||
(AsyncStorage.setItem as jest.Mock).mockResolvedValueOnce(undefined);
|
(AsyncStorage.setItem as jest.Mock).mockResolvedValueOnce(undefined);
|
||||||
const result = await saveState("SLOT1", mockState);
|
|
||||||
|
const result = await savePersistentState(mockState);
|
||||||
|
|
||||||
|
// Check that the success flag is true and message is as expected
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
expect(result.message).toBe("State saved to SLOT1");
|
expect(result.message).toBe("State saved successfully");
|
||||||
expect(AsyncStorage.setItem).toHaveBeenCalledWith("@poker_state_slot1", JSON.stringify(mockState));
|
|
||||||
|
// Check that AsyncStorage.setItem was called with the correct parameters
|
||||||
|
expect(AsyncStorage.setItem).toHaveBeenCalledWith(
|
||||||
|
"@poker_calculator_state",
|
||||||
|
JSON.stringify(mockState)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fail to save state if an error occurs", async () => {
|
it("should fail to save state if an error occurs", async () => {
|
||||||
(AsyncStorage.setItem as jest.Mock).mockRejectedValueOnce(new Error("Failed to save"));
|
// Mocking AsyncStorage.setItem to reject with an error
|
||||||
const result = await saveState("SLOT1", mockState);
|
(AsyncStorage.setItem as jest.Mock).mockRejectedValueOnce(
|
||||||
|
new Error("Failed to save")
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await savePersistentState(mockState);
|
||||||
|
|
||||||
|
// Check that the success flag is false and message is as expected
|
||||||
expect(result.success).toBe(false);
|
expect(result.success).toBe(false);
|
||||||
expect(result.message).toBe("Failed to save state");
|
expect(result.message).toBe("Failed to save state");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should load state successfully", async () => {
|
it("should load state successfully", async () => {
|
||||||
(AsyncStorage.getItem as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockState));
|
// Mocking AsyncStorage.getItem to resolve with the mockState
|
||||||
const result = await loadState("SLOT1");
|
(AsyncStorage.getItem as jest.Mock).mockResolvedValueOnce(
|
||||||
|
JSON.stringify(mockState)
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await loadPersistentState();
|
||||||
|
|
||||||
|
// Check that the loaded state matches the mockState
|
||||||
expect(result).toEqual(mockState);
|
expect(result).toEqual(mockState);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return null if no state is found", async () => {
|
it("should load default state if no saved state is found", async () => {
|
||||||
|
// Mocking AsyncStorage.getItem to return null (no saved state)
|
||||||
(AsyncStorage.getItem as jest.Mock).mockResolvedValueOnce(null);
|
(AsyncStorage.getItem as jest.Mock).mockResolvedValueOnce(null);
|
||||||
const result = await loadState("SLOT1");
|
|
||||||
expect(result).toBeNull();
|
const result = await loadPersistentState();
|
||||||
|
|
||||||
|
// Check that the default state is returned
|
||||||
|
expect(result).toEqual({
|
||||||
|
playerCount: 0,
|
||||||
|
buyInAmount: null,
|
||||||
|
numberOfChips: 0,
|
||||||
|
totalChipsCount: [],
|
||||||
|
selectedCurrency: "$",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return null if an error occurs while loading state", async () => {
|
it("should return default state if an error occurs while loading", async () => {
|
||||||
(AsyncStorage.getItem as jest.Mock).mockRejectedValueOnce(new Error("Failed to load"));
|
// Mocking AsyncStorage.getItem to reject with an error
|
||||||
const result = await loadState("SLOT1");
|
(AsyncStorage.getItem as jest.Mock).mockRejectedValueOnce(
|
||||||
expect(result).toBeNull();
|
new Error("Failed to load")
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await loadPersistentState();
|
||||||
|
|
||||||
|
// Check that the default state is returned on error
|
||||||
|
expect(result).toEqual({
|
||||||
|
playerCount: 0,
|
||||||
|
buyInAmount: null,
|
||||||
|
numberOfChips: 0,
|
||||||
|
totalChipsCount: [],
|
||||||
|
selectedCurrency: "$",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
16
package-lock.json
generated
16
package-lock.json
generated
@ -10,9 +10,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "14.0.4",
|
"@expo/vector-icons": "14.0.4",
|
||||||
"@react-native-async-storage/async-storage": "^2.1.1",
|
"@react-native-async-storage/async-storage": "^2.1.1",
|
||||||
|
"@react-native-picker/picker": "^2.11.0",
|
||||||
"@react-navigation/bottom-tabs": "7.2.0",
|
"@react-navigation/bottom-tabs": "7.2.0",
|
||||||
"@react-navigation/native": "7.0.14",
|
"@react-navigation/native": "7.0.14",
|
||||||
"expo": "^52.0.37",
|
"expo": "52.0.37",
|
||||||
"expo-blur": "14.0.3",
|
"expo-blur": "14.0.3",
|
||||||
"expo-constants": "17.0.7",
|
"expo-constants": "17.0.7",
|
||||||
"expo-file-system": "18.0.11",
|
"expo-file-system": "18.0.11",
|
||||||
@ -3926,6 +3927,19 @@
|
|||||||
"react-native": "^0.0.0-0 || >=0.65 <1.0"
|
"react-native": "^0.0.0-0 || >=0.65 <1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-native-picker/picker": {
|
||||||
|
"version": "2.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.11.0.tgz",
|
||||||
|
"integrity": "sha512-QuZU6gbxmOID5zZgd/H90NgBnbJ3VV6qVzp6c7/dDrmWdX8S0X5YFYgDcQFjE3dRen9wB9FWnj2VVdPU64adSg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"example"
|
||||||
|
],
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@react-native/assets-registry": {
|
"node_modules/@react-native/assets-registry": {
|
||||||
"version": "0.76.7",
|
"version": "0.76.7",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.7.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.7.tgz",
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "14.0.4",
|
"@expo/vector-icons": "14.0.4",
|
||||||
"@react-native-async-storage/async-storage": "^2.1.1",
|
"@react-native-async-storage/async-storage": "^2.1.1",
|
||||||
|
"@react-native-picker/picker": "^2.11.0",
|
||||||
"@react-navigation/bottom-tabs": "7.2.0",
|
"@react-navigation/bottom-tabs": "7.2.0",
|
||||||
"@react-navigation/native": "7.0.14",
|
"@react-navigation/native": "7.0.14",
|
||||||
"expo": "52.0.37",
|
"expo": "52.0.37",
|
||||||
|
Loading…
Reference in New Issue
Block a user
For a global setting like this, contexts might be a better choice.
If our app was to grow and gain complexity, we would want to avoid the inevitable 'prop drilling' as described in that article.
However, you do not need to change this. Just be aware of the context feature for future reference and personal learning. Passing as props is ok for our circumstances given the scope of our app.
Thanks for the feedback! I’ll keep that in mind for future improvements.