Merge pull request #46 from djwesty/vutukuri15/34
Implemented Currency Selector # 34
This commit is contained in:
commit
8cc8f006df
131
app/index.tsx
131
app/index.tsx
@ -1,12 +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,
|
||||||
|
TouchableOpacity,
|
||||||
|
} from "react-native";
|
||||||
|
import { FontAwesome } from "@expo/vector-icons";
|
||||||
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";
|
||||||
|
|
||||||
export enum COLORS {
|
export enum COLORS {
|
||||||
"white",
|
"white",
|
||||||
@ -21,8 +34,9 @@ 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
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadPersistentData = async () => {
|
const loadPersistentData = async () => {
|
||||||
try {
|
try {
|
||||||
@ -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);
|
||||||
setBuyInAmount(savedState.buyInAmount);
|
setBuyInAmount(savedState.buyInAmount || 20);
|
||||||
setNumberOfChips(savedState.numberOfChips);
|
setNumberOfChips(savedState.numberOfChips || 5);
|
||||||
setTotalChipsCount(savedState.totalChipsCount);
|
setTotalChipsCount(savedState.totalChipsCount || []);
|
||||||
|
setSelectedCurrency(savedState.selectedCurrency || "$");
|
||||||
} 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,95 @@ 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} />
|
<TouchableOpacity
|
||||||
<ChipDetection updateChipCount={(chipData) => {
|
onPress={() => setIsSettingsVisible(!isSettingsVisible)}
|
||||||
const chipCountArray = Object.values(chipData);
|
>
|
||||||
setTotalChipsCount(chipCountArray);
|
<Text>
|
||||||
}} />
|
<FontAwesome name="cog" size={30} color="black" />
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{isSettingsVisible && (
|
||||||
|
<View style={styles.settingsContainer}>
|
||||||
|
<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} />
|
<View style={styles.buttonContainer}>
|
||||||
<Button title="Load from Slot 1" onPress={() => handleLoad("SLOT1")} />
|
<Button
|
||||||
<Button title="Load from Slot 2" onPress={() => handleLoad("SLOT2")} />
|
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 2" onPress={() => handleLoad("SLOT2")} />
|
||||||
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IndexScreen;
|
const styles = StyleSheet.create({
|
||||||
|
header: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
settingsContainer: {
|
||||||
|
marginBottom: 20,
|
||||||
|
padding: 10,
|
||||||
|
backgroundColor: "#f5f5f5",
|
||||||
|
borderRadius: 5,
|
||||||
|
},
|
||||||
|
settingTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: "bold",
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
buttonContainer: {
|
||||||
|
marginTop: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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="$" />
|
||||||
|
<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: "$",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
101
package-lock.json
generated
101
package-lock.json
generated
@ -10,6 +10,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",
|
||||||
@ -33,6 +34,7 @@
|
|||||||
"react-native-reanimated": "3.16.7",
|
"react-native-reanimated": "3.16.7",
|
||||||
"react-native-safe-area-context": "4.12.0",
|
"react-native-safe-area-context": "4.12.0",
|
||||||
"react-native-screens": "4.4.0",
|
"react-native-screens": "4.4.0",
|
||||||
|
"react-native-vector-icons": "^10.2.0",
|
||||||
"react-native-web": "0.19.13",
|
"react-native-web": "0.19.13",
|
||||||
"react-native-webview": "13.12.5"
|
"react-native-webview": "13.12.5"
|
||||||
},
|
},
|
||||||
@ -3926,6 +3928,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",
|
||||||
@ -14431,6 +14446,92 @@
|
|||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native-vector-icons": {
|
||||||
|
"version": "10.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.2.0.tgz",
|
||||||
|
"integrity": "sha512-n5HGcxUuVaTf9QJPs/W22xQpC2Z9u0nb0KgLPnVltP8vdUvOp6+R26gF55kilP/fV4eL4vsAHUqUjewppJMBOQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"yargs": "^16.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"fa-upgrade.sh": "bin/fa-upgrade.sh",
|
||||||
|
"fa5-upgrade": "bin/fa5-upgrade.sh",
|
||||||
|
"fa6-upgrade": "bin/fa6-upgrade.sh",
|
||||||
|
"generate-icon": "bin/generate-icon.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-vector-icons/node_modules/cliui": {
|
||||||
|
"version": "7.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||||
|
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-vector-icons/node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/react-native-vector-icons/node_modules/string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-vector-icons/node_modules/strip-ansi": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-vector-icons/node_modules/yargs": {
|
||||||
|
"version": "16.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||||
|
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^7.0.2",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^20.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-vector-icons/node_modules/yargs-parser": {
|
||||||
|
"version": "20.2.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
|
||||||
|
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-native-web": {
|
"node_modules/react-native-web": {
|
||||||
"version": "0.19.13",
|
"version": "0.19.13",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.13.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.13.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