add svg icons. running into scroll issue. will upgrade packages.
This commit is contained in:
@ -1,8 +1,11 @@
|
||||
import { MeasurementInput } from "./MeasurementInput";
|
||||
import { area_t, dimensions_t } from "@/lib/dimensions_t";
|
||||
import { area_t, dimensions_t } from "@/lib/dimensions";
|
||||
import { Length } from "convert";
|
||||
import { useState } from "react";
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
import MeasurementUnitInput from "./MeasurementUnitInput";
|
||||
import { useAppDispatch, useAppSelector } from "@/app/store";
|
||||
import { selectPlywoodCalc } from "@/features/product/productSlice";
|
||||
|
||||
export type AreaInputProps = {
|
||||
onMeasurementSet?: (area : dimensions_t) => any,
|
||||
@ -14,48 +17,55 @@ export type AreaInputProps = {
|
||||
|
||||
export function AreaInput({onMeasurementSet, lengthLabel, widthLabel, defaultValue, units} : AreaInputProps) {
|
||||
|
||||
|
||||
defaultValue = defaultValue || {l: 0, w: 0, u: "ft"}
|
||||
units = units || "ft"
|
||||
|
||||
const [area, setArea] = useState(defaultValue)
|
||||
|
||||
function doOnLengthSet(measurement : dimensions_t) {
|
||||
setArea({
|
||||
...area,
|
||||
l: measurement.l
|
||||
});
|
||||
onMeasurementSet && onMeasurementSet({
|
||||
...area,
|
||||
l: measurement.l
|
||||
});
|
||||
function doOnLengthSet(l: number) {
|
||||
const a : area_t = { ...area, l };
|
||||
setArea(a);
|
||||
onMeasurementSet && onMeasurementSet(a);
|
||||
}
|
||||
|
||||
function doOnWidthSet(measurement : dimensions_t) {
|
||||
setArea({
|
||||
...area,
|
||||
w: measurement.l
|
||||
});
|
||||
onMeasurementSet && onMeasurementSet({
|
||||
...area,
|
||||
w: measurement.l
|
||||
});
|
||||
function doOnLengthUnitSet(u: Length) {
|
||||
const a : area_t = { ...area, u };
|
||||
setArea(a);
|
||||
onMeasurementSet && onMeasurementSet(a);
|
||||
}
|
||||
|
||||
function doOnWidthSet(l: number) {
|
||||
const a : area_t = { ...area, l };
|
||||
setArea(a);
|
||||
onMeasurementSet && onMeasurementSet(a);
|
||||
}
|
||||
|
||||
function doOnWidthUnitSet(u: Length) {
|
||||
const a : area_t = { ...area, u };
|
||||
setArea(a);
|
||||
onMeasurementSet && onMeasurementSet(a);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.areaInputWrapper}>
|
||||
<MeasurementInput
|
||||
defaultValue={{l: area.l, u: area.u}}
|
||||
<MeasurementUnitInput
|
||||
label="Length"
|
||||
defaultValue={0}
|
||||
defaultUnit={units}
|
||||
onValueSet={doOnLengthSet}
|
||||
label={lengthLabel}
|
||||
units={units}
|
||||
/>
|
||||
onUnitSet={doOnLengthUnitSet}
|
||||
aria-label="length"
|
||||
/>
|
||||
<Text style={{fontSize: 30,}} > x </Text>
|
||||
<MeasurementInput
|
||||
defaultValue={{l: area.w, u: area.u}}
|
||||
<MeasurementUnitInput
|
||||
label="Width"
|
||||
defaultValue={0}
|
||||
defaultUnit={units}
|
||||
onValueSet={doOnWidthSet}
|
||||
label={widthLabel}
|
||||
units={units}
|
||||
/>
|
||||
onUnitSet={doOnWidthUnitSet}
|
||||
aria-label="width"
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
@ -32,17 +32,23 @@ export const AreaRugTag = (props: AreaRugTagProps) => {
|
||||
)
|
||||
};
|
||||
|
||||
const BIG_FONT_SIZE = 30;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
component: {
|
||||
paddingVertical: 100,
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
},
|
||||
dimensions: {
|
||||
fontSize: BIG_FONT_SIZE,
|
||||
},
|
||||
price: {
|
||||
fontSize: BIG_FONT_SIZE,
|
||||
},
|
||||
date: {
|
||||
fontSize: BIG_FONT_SIZE,
|
||||
},
|
||||
tagColor: {
|
||||
fontSize: BIG_FONT_SIZE,
|
||||
},
|
||||
})
|
@ -1,19 +1,17 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { View, Text, TextInput, Button, StyleSheet } from "react-native";
|
||||
import {
|
||||
productPriceFor,
|
||||
priceDisplay,
|
||||
pricePerUnitDisplay,
|
||||
Product,
|
||||
} from "@/lib/product";
|
||||
import { useEffect, useState } from "react";
|
||||
import { View, Text, StyleSheet } from "react-native";
|
||||
import { Product } from "@/lib/product";
|
||||
import { selectProducts } from "@/features/product/productSlice";
|
||||
import { area_t, diameterToLength, length_t } from "@/lib/dimensions";
|
||||
import { useAppSelector } from "../app/store";
|
||||
import { AreaRugTag } from "@/components/AreaRugTag";
|
||||
import { Length } from "convert";
|
||||
import convert, { Length } from "convert";
|
||||
import ProductList from "@/components/ProductList";
|
||||
import { HelpfulMeasurementUnitInput } from "./HelpfulMeasurementInput";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
|
||||
const DEFAULT_UNIT: Length = "ft";
|
||||
const DEFAULT_DIAMETER_UNIT: Length = "in";
|
||||
const DEFAULT_LENGTH_UNIT: Length = "ft";
|
||||
|
||||
export const CarpetRollCalculator = () => {
|
||||
const products = useAppSelector(selectProducts);
|
||||
@ -21,26 +19,40 @@ export const CarpetRollCalculator = () => {
|
||||
const [width, setWidth] = useState(0);
|
||||
const [outerDiameter, setOuterDiameter] = useState<length_t>({
|
||||
l: 0,
|
||||
u: DEFAULT_UNIT,
|
||||
u: DEFAULT_DIAMETER_UNIT,
|
||||
});
|
||||
const [innerDiameter, setInnerDiameter] = useState<length_t>({
|
||||
l: 0,
|
||||
u: DEFAULT_UNIT,
|
||||
u: DEFAULT_DIAMETER_UNIT,
|
||||
});
|
||||
const [numRings, setNumRings] = useState(0);
|
||||
const [price, setPrice] = useState(0);
|
||||
const [rugDimensions, setRugDimensions] = useState<area_t>({
|
||||
u: DEFAULT_UNIT,
|
||||
u: DEFAULT_LENGTH_UNIT,
|
||||
w: 0,
|
||||
l: 0,
|
||||
});
|
||||
const [selectedProduct, setSelectedProduct] = useState<Product | null>(null);
|
||||
const [units, setUnits] = useState<Length>(DEFAULT_UNIT);
|
||||
const [units, setUnits] = useState<Length>(DEFAULT_LENGTH_UNIT);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(`recalculating...`);
|
||||
// convert the "diameter" units to the length unit.
|
||||
|
||||
const outerD2Value = convert(outerDiameter.l, outerDiameter.u).to(units);
|
||||
const innerD2Value = convert(innerDiameter.l, innerDiameter.u).to(units);
|
||||
|
||||
const innerD2 = {
|
||||
l: innerD2Value,
|
||||
u: units,
|
||||
};
|
||||
const outerD2 = {
|
||||
l: outerD2Value,
|
||||
u: units,
|
||||
};
|
||||
|
||||
const l = diameterToLength(outerD2, innerD2, numRings).l;
|
||||
|
||||
const dimens = {
|
||||
l: diameterToLength(outerDiameter, innerDiameter, numRings).l || 0.0,
|
||||
l,
|
||||
w: width || selectedProduct?.dimensions.l || 0.0,
|
||||
u: units || selectedProduct?.dimensions.u || "ft",
|
||||
};
|
||||
@ -50,46 +62,65 @@ export const CarpetRollCalculator = () => {
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{selectedProduct && (
|
||||
<AreaRugTag dimensions={rugDimensions} product={selectedProduct} />
|
||||
)}
|
||||
<View>
|
||||
<Text>Length Calculation</Text>
|
||||
<View>
|
||||
<Text>Outer Diameter:</Text>
|
||||
<TextInput
|
||||
aria-label="outer diameter"
|
||||
onChangeText={(text) =>
|
||||
setOuterDiameter({ l: Number(text), u: units })
|
||||
}
|
||||
/>
|
||||
<Text>Inner Diameter:</Text>
|
||||
<TextInput
|
||||
aria-label="inner diameter"
|
||||
onChangeText={(text) =>
|
||||
setInnerDiameter({ l: Number(text), u: units })
|
||||
}
|
||||
/>
|
||||
<Text>Number of rings:</Text>
|
||||
<TextInput
|
||||
aria-label="number of rings"
|
||||
onChangeText={(text) => setNumRings(Number(text))}
|
||||
/>
|
||||
</View>
|
||||
{selectedProduct ? (
|
||||
<AreaRugTag dimensions={rugDimensions} product={selectedProduct} />
|
||||
) : (
|
||||
<Text style={styles.placeholder}>Please Select a Product</Text>
|
||||
)}
|
||||
</View>
|
||||
<View>
|
||||
<Text>Width:</Text>
|
||||
<TextInput
|
||||
aria-label="width"
|
||||
onChangeText={(text) => setWidth(Number(text))}
|
||||
/>
|
||||
</View>
|
||||
<Text>Price: {priceDisplay(price)}</Text>
|
||||
<Text>
|
||||
{selectedProduct ? pricePerUnitDisplay(selectedProduct) : "0.00"}
|
||||
</Text>
|
||||
<View style={styles.container}>
|
||||
<ProductList onProductSelected={setSelectedProduct} />
|
||||
<View style={{ flex: 1, }}>
|
||||
<ScrollView>
|
||||
<View style={styles.inputFields}>
|
||||
<View style={styles.inputFieldWrapper}>
|
||||
<HelpfulMeasurementUnitInput
|
||||
label="Length"
|
||||
svgUri="/assets/images/icons/carpet-roll-length-raw.svg"
|
||||
onUnitSet={setUnits}
|
||||
onValueSet={setWidth}
|
||||
defaultValue={width}
|
||||
defaultUnit={units}
|
||||
unitChoices={["ft", "in"]}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.inputFieldWrapper}>
|
||||
<HelpfulMeasurementUnitInput
|
||||
label="Inner diameter"
|
||||
svgUri="/assets/images/icons/carpet-roll-length-inner-diameter-raw.svg"
|
||||
onUnitSet={(u) => setInnerDiameter({ ...innerDiameter, u })}
|
||||
defaultValue={innerDiameter.l}
|
||||
defaultUnit={innerDiameter.u}
|
||||
unitChoices={["ft", "in"]}
|
||||
onValueSet={(l) => setInnerDiameter({ ...innerDiameter, l })}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.inputFieldWrapper}>
|
||||
<HelpfulMeasurementUnitInput
|
||||
label="Outer diameter"
|
||||
svgUri="/assets/images/icons/carpet-roll-length-outer-diameter-raw.svg"
|
||||
onUnitSet={(u) => setOuterDiameter({ ...outerDiameter, u })}
|
||||
defaultValue={innerDiameter.l}
|
||||
defaultUnit={innerDiameter.u}
|
||||
unitChoices={["ft", "in"]}
|
||||
onValueSet={(l) => setOuterDiameter({ ...outerDiameter, l })}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.inputFieldWrapper}>
|
||||
<HelpfulMeasurementUnitInput
|
||||
label="Number of rings"
|
||||
svgUri="/assets/images/icons/carpet-roll-length-number-of-rings-raw.svg"
|
||||
defaultValue={0}
|
||||
onValueSet={setNumRings}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.container}>
|
||||
<ProductList
|
||||
onProductSelected={setSelectedProduct}
|
||||
productType="area_rug"
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@ -97,9 +128,29 @@ export const CarpetRollCalculator = () => {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexGrow: 1,
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
padding: 20,
|
||||
},
|
||||
placeholder: {
|
||||
alignContent: "center",
|
||||
alignSelf: "center",
|
||||
paddingTop: 50,
|
||||
paddingBottom: 50,
|
||||
fontSize: 30,
|
||||
},
|
||||
inputFieldWrapper: {
|
||||
padding: 10,
|
||||
},
|
||||
inputFields: {},
|
||||
label: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
},
|
||||
numberInput: {
|
||||
flexDirection: "row",
|
||||
borderStyle: "solid",
|
||||
borderColor: "black",
|
||||
borderWidth: 1,
|
||||
},
|
||||
});
|
||||
|
||||
|
33
components/HelpfulMeasurementInput.tsx
Normal file
33
components/HelpfulMeasurementInput.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
import { MeasurementInputProps } from "./MeasurementInput";
|
||||
import MeasurementUnitInput, {
|
||||
MeasurementUnitInputProps,
|
||||
} from "./MeasurementUnitInput";
|
||||
import { SvgUri } from "react-native-svg";
|
||||
import { Length } from "convert";
|
||||
|
||||
export type HelpfulMeasurementUnitInputParams = MeasurementUnitInputProps & {
|
||||
svgUri: string;
|
||||
label: string;
|
||||
unitChoices?: Length[];
|
||||
};
|
||||
|
||||
export function HelpfulMeasurementUnitInput(
|
||||
props: HelpfulMeasurementUnitInputParams
|
||||
) {
|
||||
return (
|
||||
<View>
|
||||
<SvgUri uri={props.svgUri} width="100px" height="100px" />
|
||||
<Text>{props.label}</Text>
|
||||
<MeasurementUnitInput
|
||||
defaultUnit={props.defaultUnit || "ft"}
|
||||
defaultValue={props.defaultValue || 0.0}
|
||||
onUnitSet={props.onUnitSet}
|
||||
onValueSet={props.onValueSet}
|
||||
unitChoices={props.unitChoices}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({});
|
@ -1,46 +1,24 @@
|
||||
import { dimensions_t, length_t } from "@/lib/dimensions_t";
|
||||
import { Length } from "convert";
|
||||
import { useState } from "react";
|
||||
import { StyleSheet, Text, TextInput, View } from "react-native";
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
import { NumberInput, NumberInputProps } from "./NumberInput";
|
||||
|
||||
export type t_length_unit = "foot" | "inch"
|
||||
|
||||
export type MeasurementInputProps = {
|
||||
onValueSet?: (d: dimensions_t) => any,
|
||||
defaultValue: length_t;
|
||||
label?: string,
|
||||
export type MeasurementInputProps = NumberInputProps & {
|
||||
units?: Length,
|
||||
}
|
||||
|
||||
export function MeasurementInput({onValueSet, defaultValue, label, units}: MeasurementInputProps) {
|
||||
|
||||
const [mValue, setMValue] = useState(defaultValue)
|
||||
const defValue = Number.isNaN(defaultValue.l) ? 0 : defaultValue.l
|
||||
export function MeasurementInput({onValueSet, defaultValue: defaultValue, label, units}: MeasurementInputProps) {
|
||||
|
||||
units = units || "ft";
|
||||
|
||||
function doOnValueSet(value : string) {
|
||||
setMValue(mValue);
|
||||
const iVal = parseFloat(value) || parseInt(value);
|
||||
onValueSet && onValueSet({
|
||||
...defaultValue,
|
||||
l: iVal,
|
||||
})
|
||||
}
|
||||
|
||||
const sDefValue = new String(defValue).valueOf()
|
||||
|
||||
return (
|
||||
<View style={styles.inputWrapper}>
|
||||
<TextInput
|
||||
clearTextOnFocus={true}
|
||||
defaultValue={sDefValue}
|
||||
onChangeText={doOnValueSet}
|
||||
inputMode='decimal'
|
||||
style={styles.lengthInput}
|
||||
aria-label={label || "Enter measurement"}
|
||||
/>
|
||||
<Text style={styles.unitHints}>{units}</Text>
|
||||
<NumberInput
|
||||
onValueSet={v => onValueSet && onValueSet(v)}
|
||||
defaultValue={defaultValue}
|
||||
label={label}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
51
components/MeasurementUnitInput.tsx
Normal file
51
components/MeasurementUnitInput.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import { dimensions_t, length_t } from "@/lib/dimensions";
|
||||
import { Length } from "convert";
|
||||
import { MeasurementInput, MeasurementInputProps } from "./MeasurementInput";
|
||||
import UnitChooser, {
|
||||
UnitChooserPropsBase,
|
||||
} from "./UnitChooser";
|
||||
import { StyleSheet, View } from "react-native";
|
||||
|
||||
export type MeasurementUnitInputProps = MeasurementInputProps &
|
||||
UnitChooserPropsBase & {
|
||||
defaultValue: number;
|
||||
unitChoices?: Length[];
|
||||
};
|
||||
|
||||
export default function MeasurementUnitInput({
|
||||
onValueSet,
|
||||
onUnitSet,
|
||||
defaultValue,
|
||||
unitChoices,
|
||||
defaultUnit,
|
||||
label,
|
||||
units,
|
||||
}: MeasurementUnitInputProps) {
|
||||
return (
|
||||
<View style={unitChoices ? styles.inputRow : styles.inputCol}>
|
||||
<MeasurementInput
|
||||
onValueSet={onValueSet}
|
||||
defaultValue={defaultValue}
|
||||
label={label}
|
||||
units={units}
|
||||
/>
|
||||
{unitChoices && (
|
||||
<UnitChooser
|
||||
defaultUnit={defaultUnit}
|
||||
choices={unitChoices}
|
||||
onUnitSet={onUnitSet}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
inputRow: {
|
||||
flexDirection: "row",
|
||||
},
|
||||
inputCol: {
|
||||
|
||||
}
|
||||
})
|
54
components/NumberInput.tsx
Normal file
54
components/NumberInput.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import { StyleSheet, TextInput } from "react-native";
|
||||
|
||||
export type NumberInputProps = {
|
||||
defaultValue: number;
|
||||
onValueSet: (value: number) => any;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
export function NumberInput({
|
||||
defaultValue,
|
||||
onValueSet,
|
||||
label,
|
||||
}: NumberInputProps) {
|
||||
const defValue = Number.isNaN(defaultValue) ? 0 : defaultValue;
|
||||
|
||||
function doOnValueSet(value: string) {
|
||||
const iVal = parseFloat(value) || parseInt(value);
|
||||
onValueSet && onValueSet(iVal);
|
||||
}
|
||||
|
||||
const sDefValue = new String(defValue).valueOf();
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
clearTextOnFocus={true}
|
||||
defaultValue={sDefValue}
|
||||
onChangeText={doOnValueSet}
|
||||
inputMode="decimal"
|
||||
style={styles.numberInput}
|
||||
aria-label={label || "Enter measurement"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
inputWrapper: {
|
||||
alignItems: "flex-start",
|
||||
flexDirection: "row",
|
||||
verticalAlign: "middle",
|
||||
},
|
||||
unitHints: {
|
||||
padding: 10,
|
||||
fontSize: 20,
|
||||
verticalAlign: "middle",
|
||||
},
|
||||
numberInput: {
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
borderColor: "grey",
|
||||
padding: 4,
|
||||
margin: 4,
|
||||
fontSize: 25,
|
||||
},
|
||||
});
|
@ -1,207 +1,197 @@
|
||||
import { Product, productPriceFor } from '@/lib/product';
|
||||
import { Product, productPriceFor } from "@/lib/product";
|
||||
import { dimensions_t } from "@/lib/dimensions";
|
||||
import { useState, useEffect } from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import PriceDisplay from './Price';
|
||||
import { AreaInput } from './AreaInput';
|
||||
import { MeasurementInput } from './MeasurementInput';
|
||||
import ProductList from './ProductList';
|
||||
import UnitChooser from './UnitChooser';
|
||||
import convert, { Length } from 'convert';
|
||||
import PercentDamage from './PercentDamange';
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { View, Text, StyleSheet } from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import PriceDisplay from "./Price";
|
||||
import { AreaInput } from "./AreaInput";
|
||||
import { MeasurementInput } from "./MeasurementInput";
|
||||
import ProductList from "./ProductList";
|
||||
import UnitChooser from "./UnitChooser";
|
||||
import convert, { Length } from "convert";
|
||||
import PercentDamage from "./PercentDamange";
|
||||
import MeasurementUnitInput from "./MeasurementUnitInput";
|
||||
|
||||
export default function ProductCalculatorSelector() {
|
||||
const [activeProduct, setActiveProduct] = useState(null as Product | null);
|
||||
const [price, setPrice] = useState(0);
|
||||
const [measurement, setMeasurement] = useState({
|
||||
l: 0,
|
||||
w: 0,
|
||||
u: "ft",
|
||||
} as dimensions_t);
|
||||
const [percentDamage, setPercentDamange] = useState(0.0);
|
||||
|
||||
const [activeProduct, setActiveProduct] = useState(null as Product | null);
|
||||
const [price, setPrice] = useState(0);
|
||||
const [measurement, setMeasurement] = useState({ l: 0, w: 0, u: "ft" } as dimensions_t);
|
||||
const [percentDamage, setPercentDamange] = useState(0.0);
|
||||
useEffect(
|
||||
function () {
|
||||
const iv = setInterval(function () {
|
||||
if (!(activeProduct && measurement)) return;
|
||||
setPrice(productPriceFor(activeProduct, measurement, percentDamage));
|
||||
}, 50);
|
||||
return function () {
|
||||
clearInterval(iv);
|
||||
};
|
||||
},
|
||||
[activeProduct, measurement, percentDamage]
|
||||
);
|
||||
|
||||
useEffect(function () {
|
||||
const iv = setInterval(function () {
|
||||
if (!(activeProduct && measurement)) return;
|
||||
setPrice(
|
||||
productPriceFor(activeProduct, measurement, percentDamage)
|
||||
function onMeasurementSet(dimensions: dimensions_t) {
|
||||
setMeasurement(dimensions);
|
||||
activeProduct &&
|
||||
setPrice(productPriceFor(activeProduct, measurement, percentDamage));
|
||||
}
|
||||
|
||||
function onLengthSet(l: number) {
|
||||
setMeasurement({ ...measurement, l });
|
||||
onMeasurementSet && onMeasurementSet({ ...measurement, l });
|
||||
}
|
||||
|
||||
function onUnitChosen(unit: Length) {
|
||||
setMeasurement({
|
||||
...measurement,
|
||||
u: unit,
|
||||
});
|
||||
}
|
||||
|
||||
function onSetPercentDamage(pct: number) {
|
||||
setPercentDamange(pct);
|
||||
}
|
||||
|
||||
function onProductSelected(product: Product) {
|
||||
setActiveProduct(product);
|
||||
setMeasurement({
|
||||
l: convert(product.dimensions.l, product.dimensions.u).to(measurement.u),
|
||||
u: measurement.u,
|
||||
...("w" in measurement && "w" in product.dimensions
|
||||
? {
|
||||
w: convert(product.dimensions.w, product.dimensions.u).to(
|
||||
measurement.u
|
||||
),
|
||||
u: measurement.u,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.wrapper}>
|
||||
<PriceDisplay price={price} />
|
||||
<View style={styles.inputAndUnitWrapper}>
|
||||
<View style={styles.inputWrapper}>
|
||||
{activeProduct ? (
|
||||
"w" in activeProduct.dimensions ? (
|
||||
<AreaInput
|
||||
defaultValue={activeProduct.dimensions}
|
||||
onMeasurementSet={onMeasurementSet}
|
||||
widthLabel="enter width"
|
||||
lengthLabel="enter length"
|
||||
units={measurement.u}
|
||||
/>
|
||||
) : (
|
||||
<MeasurementUnitInput
|
||||
defaultValue={activeProduct.dimensions.l}
|
||||
onValueSet={onLengthSet}
|
||||
onUnitSet={onUnitChosen}
|
||||
defaultUnit={activeProduct.dimensions.u}
|
||||
/>
|
||||
)
|
||||
}, 50);
|
||||
return function () {
|
||||
clearInterval(iv);
|
||||
};
|
||||
}, [activeProduct, measurement, percentDamage]);
|
||||
|
||||
function onMeasurementSet(dimensions: dimensions_t) {
|
||||
setMeasurement(dimensions);
|
||||
activeProduct && setPrice(
|
||||
productPriceFor(activeProduct, measurement, percentDamage)
|
||||
)
|
||||
}
|
||||
|
||||
function onUnitChosen(unit: Length) {
|
||||
setMeasurement({
|
||||
...measurement,
|
||||
u: unit,
|
||||
});
|
||||
}
|
||||
|
||||
function onSetPercentDamage(pct: number) {
|
||||
setPercentDamange(pct);
|
||||
}
|
||||
|
||||
function onProductSelected(product: Product) {
|
||||
setActiveProduct(product);
|
||||
setMeasurement(
|
||||
{
|
||||
l: convert(
|
||||
product.dimensions.l,
|
||||
product.dimensions.u,
|
||||
).to(measurement.u),
|
||||
u: measurement.u,
|
||||
...(
|
||||
("w" in measurement && "w" in product.dimensions) ? {
|
||||
w: convert(
|
||||
product.dimensions.w,
|
||||
product.dimensions.u,
|
||||
).to(measurement.u),
|
||||
u: measurement.u,
|
||||
} : {}
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.wrapper}>
|
||||
<PriceDisplay price={price} />
|
||||
<View style={styles.inputAndUnitWrapper}>
|
||||
<View style={styles.inputWrapper}>
|
||||
{
|
||||
activeProduct ? (
|
||||
"w" in activeProduct.dimensions ?
|
||||
<AreaInput
|
||||
defaultValue={activeProduct.dimensions}
|
||||
onMeasurementSet={onMeasurementSet}
|
||||
widthLabel='enter width'
|
||||
lengthLabel='enter length'
|
||||
units={measurement.u}
|
||||
/>
|
||||
:
|
||||
<MeasurementInput
|
||||
defaultValue={activeProduct.dimensions}
|
||||
onValueSet={onMeasurementSet}
|
||||
label="enter length"
|
||||
units={measurement.u}
|
||||
/>
|
||||
|
||||
) : (
|
||||
<Text>Please select a product</Text>
|
||||
)
|
||||
}
|
||||
{
|
||||
activeProduct && <UnitChooser choices={["in", "ft"]} onChoicePressed={onUnitChosen} />
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
{activeProduct &&
|
||||
(<View style={styles.damageWrapper}>
|
||||
<PercentDamage
|
||||
onSetPercentage={onSetPercentDamage}
|
||||
/>
|
||||
</View>)
|
||||
}
|
||||
<ProductList onProductSelected={onProductSelected} />
|
||||
</SafeAreaView>
|
||||
);
|
||||
) : (
|
||||
<Text>Please select a product</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
{activeProduct && (
|
||||
<View style={styles.damageWrapper}>
|
||||
<PercentDamage onSetPercentage={onSetPercentDamage} />
|
||||
</View>
|
||||
)}
|
||||
<ProductList onProductSelected={onProductSelected} productType="lumber" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export const styles = StyleSheet.create({
|
||||
wrapper: {
|
||||
overflow: "scroll"
|
||||
},
|
||||
bigPriceWrapper: {
|
||||
alignContent: "center",
|
||||
},
|
||||
bigPrice: {
|
||||
alignSelf: "center",
|
||||
fontSize: 40,
|
||||
marginTop: 100,
|
||||
marginBottom: 100,
|
||||
},
|
||||
inputWrapper: {
|
||||
flexDirection: "row",
|
||||
alignItems: "flex-start",
|
||||
verticalAlign: "middle",
|
||||
},
|
||||
unitSelector: {
|
||||
},
|
||||
inputAndUnitWrapper: {
|
||||
flexDirection: "row",
|
||||
alignSelf: "center",
|
||||
},
|
||||
widthInput: {
|
||||
width: 200,
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
borderColor: "grey",
|
||||
padding: 4,
|
||||
margin: 4,
|
||||
fontSize: 30,
|
||||
},
|
||||
activeProduct: {
|
||||
borderWidth: 2,
|
||||
borderColor: "black",
|
||||
borderStyle: "solid",
|
||||
},
|
||||
inactiveProduct: {
|
||||
wrapper: {
|
||||
overflow: "scroll",
|
||||
},
|
||||
bigPriceWrapper: {
|
||||
alignContent: "center",
|
||||
},
|
||||
bigPrice: {
|
||||
alignSelf: "center",
|
||||
fontSize: 40,
|
||||
marginTop: 100,
|
||||
marginBottom: 100,
|
||||
},
|
||||
inputWrapper: {
|
||||
flexDirection: "row",
|
||||
alignItems: "flex-start",
|
||||
verticalAlign: "middle",
|
||||
},
|
||||
unitSelector: {},
|
||||
inputAndUnitWrapper: {
|
||||
flexDirection: "row",
|
||||
alignSelf: "center",
|
||||
},
|
||||
widthInput: {
|
||||
width: 200,
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
borderColor: "grey",
|
||||
padding: 4,
|
||||
margin: 4,
|
||||
fontSize: 30,
|
||||
},
|
||||
activeProduct: {
|
||||
borderWidth: 2,
|
||||
borderColor: "black",
|
||||
borderStyle: "solid",
|
||||
},
|
||||
inactiveProduct: {},
|
||||
titleContainer: {
|
||||
flexDirection: "row",
|
||||
gap: 8,
|
||||
},
|
||||
stepContainer: {
|
||||
gap: 8,
|
||||
marginBottom: 8,
|
||||
},
|
||||
reactLogo: {
|
||||
height: 178,
|
||||
width: 290,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
},
|
||||
productTileTouchable: {
|
||||
margin: 10,
|
||||
padding: 20,
|
||||
backgroundColor: "grey",
|
||||
},
|
||||
|
||||
},
|
||||
titleContainer: {
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
},
|
||||
stepContainer: {
|
||||
gap: 8,
|
||||
marginBottom: 8,
|
||||
},
|
||||
reactLogo: {
|
||||
height: 178,
|
||||
width: 290,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
},
|
||||
productTileTouchable: {
|
||||
margin: 10,
|
||||
padding: 20,
|
||||
backgroundColor: "grey",
|
||||
},
|
||||
productTileTouchableActive: {
|
||||
borderWidth: 2,
|
||||
borderStyle: "solid",
|
||||
borderColor: "black",
|
||||
margin: 10,
|
||||
padding: 20,
|
||||
},
|
||||
|
||||
productTileTouchableActive: {
|
||||
borderWidth: 2,
|
||||
borderStyle: "solid",
|
||||
borderColor: "black",
|
||||
margin: 10,
|
||||
padding: 20,
|
||||
},
|
||||
productTileText: {
|
||||
textAlign: "center",
|
||||
color: "white",
|
||||
},
|
||||
|
||||
productTileText: {
|
||||
textAlign: "center",
|
||||
color: "white",
|
||||
},
|
||||
productTileTextActive: {
|
||||
textAlign: "center",
|
||||
color: "black",
|
||||
},
|
||||
|
||||
productTileTextActive: {
|
||||
textAlign: "center",
|
||||
color: "black",
|
||||
},
|
||||
|
||||
productTileCover: {
|
||||
padding: 4,
|
||||
},
|
||||
|
||||
damageWrapper: {
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 10,
|
||||
},
|
||||
productTileCover: {
|
||||
padding: 4,
|
||||
},
|
||||
|
||||
damageWrapper: {
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 10,
|
||||
},
|
||||
});
|
||||
|
@ -3,6 +3,7 @@ import { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
FlatList,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
@ -134,13 +135,13 @@ export const ProductEditorItem = (props: ProductEditorItemProps) => {
|
||||
</Text>
|
||||
)}
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight
|
||||
<Pressable
|
||||
onPress={() => onDeleteProduct()}
|
||||
aria-label="delete product"
|
||||
style={styles.deleteProductHighlight}
|
||||
>
|
||||
<Ionicons style={styles.deleteProductButton} name="trash-outline" />
|
||||
</TouchableHighlight>
|
||||
</Pressable>
|
||||
</View>
|
||||
{showAttributes && (
|
||||
<View style={styles.detailsWrapper}>
|
||||
|
@ -17,7 +17,7 @@ export default function ProductList({
|
||||
const [activeProduct, setActiveProduct] = useState(null as null | Product);
|
||||
const products = useAppSelector(selectProducts)
|
||||
.filter((p) => !!p)
|
||||
.filter((p: Product) => productType ? p.type === productType : true)
|
||||
.filter((p: Product) => (!productType) || p.type === productType)
|
||||
.filter((p) => {
|
||||
return !!p.dimensions;
|
||||
});
|
||||
|
@ -1,74 +1,84 @@
|
||||
import { Length } from "convert";
|
||||
import { useState } from "react";
|
||||
import { Button, StyleSheet, Text, TouchableHighlight, View } from "react-native";
|
||||
import {
|
||||
Button,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
|
||||
export type UnitChooserProps = {
|
||||
choices: Length[],
|
||||
onChoicePressed: (l: Length) => any,
|
||||
activeColor?: string,
|
||||
defaultColor?: string,
|
||||
defaultValue? : Length,
|
||||
}
|
||||
export type UnitChooserPropsBase = {
|
||||
onUnitSet?: (l: Length) => any;
|
||||
activeColor?: string;
|
||||
defaultColor?: string;
|
||||
defaultUnit?: Length;
|
||||
};
|
||||
|
||||
export default function UnitChooser({ choices, onChoicePressed, activeColor, defaultColor, defaultValue }: UnitChooserProps) {
|
||||
const [value, setValue] = useState(defaultValue || choices[0] as Length);
|
||||
export type UnitChooserProps = UnitChooserPropsBase & {
|
||||
choices: Length[];
|
||||
};
|
||||
|
||||
activeColor = activeColor || "lightblue";
|
||||
defaultColor = defaultColor || "lightgrey";
|
||||
export default function UnitChooser({
|
||||
choices,
|
||||
onUnitSet,
|
||||
activeColor,
|
||||
defaultColor,
|
||||
defaultUnit,
|
||||
}: UnitChooserProps) {
|
||||
const [value, setValue] = useState(defaultUnit || (choices[0] as Length));
|
||||
|
||||
function doChoiceClicked(choice: Length) {
|
||||
setValue(choice);
|
||||
onChoicePressed(choice);
|
||||
}
|
||||
activeColor = activeColor || "lightblue";
|
||||
defaultColor = defaultColor || "lightgrey";
|
||||
|
||||
return (
|
||||
<View style={styles.unitChooser}>
|
||||
{choices.map((ci) => {
|
||||
return (
|
||||
<TouchableHighlight
|
||||
onPress={() => doChoiceClicked(ci)}
|
||||
style={value === ci ? styles.active : styles.default }
|
||||
key={ci}
|
||||
>
|
||||
<Text style={value === ci ? styles.textActive : styles.textDefault}>{ci}</Text>
|
||||
</TouchableHighlight>
|
||||
)
|
||||
})
|
||||
}
|
||||
</View>
|
||||
)
|
||||
function doChoiceClicked(choice: Length) {
|
||||
setValue(choice);
|
||||
onUnitSet && onUnitSet(choice);
|
||||
}
|
||||
|
||||
const activeColors = ['#a7caff', '#5588ff', '#5588ff', '#5588ff'];
|
||||
const inactiveColors = ['#d0d0d0', '#828282', '#828282', '#828282'];
|
||||
|
||||
return (
|
||||
<View style={styles.unitChooser}>
|
||||
{choices.map((ci) => {
|
||||
return (
|
||||
<LinearGradient
|
||||
colors={ci === value ? activeColors : inactiveColors}
|
||||
style={styles.gradientButton}
|
||||
>
|
||||
<TouchableHighlight style={{padding: 5, borderRadius: 5, }} onPress={() => doChoiceClicked(ci)} key={ci}>
|
||||
<Text style={{padding: 5, fontSize: 16}}>{ci}</Text>
|
||||
</TouchableHighlight>
|
||||
</LinearGradient>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
unitChooser: {
|
||||
flexDirection: "row",
|
||||
verticalAlign: "middle",
|
||||
},
|
||||
active: {
|
||||
backgroundColor: "skyblue",
|
||||
padding: 5,
|
||||
borderRadius: 5,
|
||||
},
|
||||
default: {
|
||||
backgroundColor: "lightgray",
|
||||
padding: 5,
|
||||
borderRadius: 5,
|
||||
verticalAlign: "middle",
|
||||
},
|
||||
textActive: {
|
||||
marginTop: 2,
|
||||
marginBottom: 2,
|
||||
marginLeft: 10,
|
||||
marginRight: 10,
|
||||
fontSize: 25,
|
||||
},
|
||||
textDefault: {
|
||||
marginTop: 2,
|
||||
marginBottom: 2,
|
||||
marginLeft: 10,
|
||||
marginRight: 10,
|
||||
fontSize: 25,
|
||||
},
|
||||
unitButton: {
|
||||
},
|
||||
})
|
||||
gradientButton: {},
|
||||
unitChooser: {
|
||||
flexDirection: "row",
|
||||
verticalAlign: "middle",
|
||||
padding: 4,
|
||||
},
|
||||
textActive: {
|
||||
marginTop: 2,
|
||||
marginBottom: 2,
|
||||
marginLeft: 10,
|
||||
marginRight: 10,
|
||||
fontSize: 25,
|
||||
},
|
||||
textDefault: {
|
||||
marginTop: 2,
|
||||
marginBottom: 2,
|
||||
marginLeft: 10,
|
||||
marginRight: 10,
|
||||
fontSize: 25,
|
||||
},
|
||||
unitButton: {},
|
||||
});
|
||||
|
@ -1,5 +1,11 @@
|
||||
import React from "react";
|
||||
import { render, fireEvent, screen, within } from "@testing-library/react-native";
|
||||
import {
|
||||
render,
|
||||
fireEvent,
|
||||
screen,
|
||||
within,
|
||||
act,
|
||||
} from "@testing-library/react-native";
|
||||
import CarpetRollCalculator from "@/components/CarpetRollCalculator";
|
||||
import { renderWithProviders } from "@/lib/rendering";
|
||||
|
||||
@ -7,6 +13,8 @@ import allProducts from "@/__fixtures__/initialProducts";
|
||||
import { Product, pricePerUnitDisplay } from "@/lib/product";
|
||||
import initialProducts from "@/__fixtures__/initialProducts";
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
const areaRugProducts = allProducts.filter((p) => "area_rug" === p.type);
|
||||
|
||||
describe("CarpetRollCalculator", () => {
|
||||
@ -15,30 +23,42 @@ describe("CarpetRollCalculator", () => {
|
||||
products: initialProducts,
|
||||
});
|
||||
|
||||
const areaRug = initialProducts.find(p => p.type === 'area_rug') as Product;
|
||||
const areaRug = initialProducts.find(
|
||||
(p) => p.type === "area_rug"
|
||||
) as Product;
|
||||
const areaRugLabel = `product ${areaRug.id}`;
|
||||
fireEvent.press(screen.getByLabelText(areaRugLabel));
|
||||
act(() => {
|
||||
fireEvent.press(screen.getByLabelText(areaRugLabel));
|
||||
});
|
||||
|
||||
// Test the interaction with the width input
|
||||
const widthInput = screen.getByLabelText("width");
|
||||
fireEvent.changeText(widthInput, "10");
|
||||
act(() => {
|
||||
fireEvent.changeText(widthInput, "10");
|
||||
});
|
||||
|
||||
// Test the interaction with the outer diameter input
|
||||
const outerDiameterInput = screen.getByLabelText("outer diameter");
|
||||
fireEvent.changeText(outerDiameterInput, "3");
|
||||
act(() => {
|
||||
fireEvent.changeText(outerDiameterInput, "3");
|
||||
});
|
||||
|
||||
// Test the interaction with the inner diameter input
|
||||
const innerDiameterInput = screen.getByLabelText("inner diameter");
|
||||
fireEvent.changeText(innerDiameterInput, "1");
|
||||
act(() => {
|
||||
fireEvent.changeText(innerDiameterInput, "1");
|
||||
});
|
||||
|
||||
// Test the interaction with the number of rings input
|
||||
const numRingsInput = screen.getByLabelText("number of rings");
|
||||
fireEvent.changeText(numRingsInput, "5");
|
||||
act(() => {
|
||||
fireEvent.changeText(numRingsInput, "5");
|
||||
});
|
||||
|
||||
jest.advanceTimersByTime(3000);
|
||||
|
||||
// Test the interaction with the price display
|
||||
const {getByText} = within(screen.getByLabelText("area rug price"));
|
||||
const { getByText } = within(screen.getByLabelText("area rug price"));
|
||||
expect(getByText(/\$.*58.*\..*19.*/)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
@ -1,49 +1,51 @@
|
||||
import { LumberProduct, Product } from "@/lib/product"
|
||||
import {ProductAttributeEditor} from "../ProductAttributeEditor"
|
||||
import { fireEvent, render, screen } from '@testing-library/react-native';
|
||||
import { LumberProduct, Product } from "@/lib/product";
|
||||
import { ProductAttributeEditor } from "../ProductAttributeEditor";
|
||||
import { fireEvent, render, screen } from "@testing-library/react-native";
|
||||
import { renderWithProviders } from "@/lib/rendering";
|
||||
|
||||
describe("Product editor tests", () => {
|
||||
const productName = "Fun Product";
|
||||
it("Product attributes can be deleted", async () => {
|
||||
const onChange = jest.fn();
|
||||
const onDelete = jest.fn();
|
||||
render(
|
||||
<ProductAttributeEditor
|
||||
attributeKey="name"
|
||||
attributeValue="product"
|
||||
onChangeAttribute={onChange}
|
||||
onDelete={onDelete}
|
||||
/>);
|
||||
expect(screen.getByLabelText("Delete Attribute")).not.toBeNull();
|
||||
fireEvent.press(await screen.getByLabelText("Delete Attribute"));
|
||||
expect(onDelete).toHaveBeenCalled();
|
||||
});
|
||||
it("Product attributes can be modified", async () => {
|
||||
const product : Product = {
|
||||
pricePerUnit: 10,
|
||||
dimensions: {
|
||||
l: 40,
|
||||
u: "ft",
|
||||
},
|
||||
type: "lumber",
|
||||
}
|
||||
const onChange = jest.fn();
|
||||
const onDelete = jest.fn();
|
||||
const onKeyChange = jest.fn();
|
||||
render(
|
||||
<ProductAttributeEditor
|
||||
attributeKey="old test key"
|
||||
attributeValue="old test value"
|
||||
onChangeAttribute={onChange}
|
||||
onDelete={onDelete}
|
||||
onChangeAttributeKey={onKeyChange}
|
||||
/>);
|
||||
fireEvent.changeText(screen.getByLabelText("Edit Key"), "new test key");
|
||||
expect(onKeyChange).toHaveBeenCalled();
|
||||
fireEvent.changeText(screen.getByLabelText("Edit Value"), "new name");
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
fireEvent.press(screen.getByLabelText("Delete Attribute"));
|
||||
expect(onDelete).toHaveBeenCalled();
|
||||
})
|
||||
|
||||
})
|
||||
const productName = "Fun Product";
|
||||
it("Product attributes can be deleted", async () => {
|
||||
const onChange = jest.fn();
|
||||
const onDelete = jest.fn();
|
||||
renderWithProviders(
|
||||
<ProductAttributeEditor
|
||||
attributeKey="name"
|
||||
attributeValue="product"
|
||||
onChangeAttribute={onChange}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByLabelText("Delete Attribute")).not.toBeNull();
|
||||
fireEvent.press(await screen.getByLabelText("Delete Attribute"));
|
||||
expect(onDelete).toHaveBeenCalled();
|
||||
});
|
||||
it("Product attributes can be modified", async () => {
|
||||
const product: Product = {
|
||||
pricePerUnit: 10,
|
||||
dimensions: {
|
||||
l: 40,
|
||||
u: "ft",
|
||||
},
|
||||
type: "lumber",
|
||||
};
|
||||
const onChange = jest.fn();
|
||||
const onDelete = jest.fn();
|
||||
const onKeyChange = jest.fn();
|
||||
render(
|
||||
<ProductAttributeEditor
|
||||
attributeKey="old test key"
|
||||
attributeValue="old test value"
|
||||
onChangeAttribute={onChange}
|
||||
onDelete={onDelete}
|
||||
onChangeAttributeKey={onKeyChange}
|
||||
/>
|
||||
);
|
||||
fireEvent.changeText(screen.getByLabelText("Edit Key"), "new test key");
|
||||
expect(onKeyChange).toHaveBeenCalled();
|
||||
fireEvent.changeText(screen.getByLabelText("Edit Value"), "new name");
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
fireEvent.press(screen.getByLabelText("Delete Attribute"));
|
||||
expect(onDelete).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -3,9 +3,10 @@ import { Provider } from 'react-redux';
|
||||
import ProductCalculatorSelector from '@/components/ProductCalculatorSelector';
|
||||
import { renderWithProviders } from '@/lib/rendering';
|
||||
import { Product, pricePerUnitDisplay, productPriceFor } from '@/lib/product';
|
||||
|
||||
import initialProducts from '@/__fixtures__/initialProducts';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
const mockAreaProduct = initialProducts.find(p => 'w' in p.dimensions ) as Product
|
||||
const mockLengthProduct = initialProducts.find(p => (!('w' in p.dimensions)) ) as Product
|
||||
|
||||
@ -40,13 +41,17 @@ describe('ProductCalculatorSelector', () => {
|
||||
expect(screen.getByText('Please select a product')).toBeTruthy();
|
||||
const areaLabel = `${mockAreaProduct.attributes?.name} (${pricePerUnitDisplay(mockAreaProduct)})`;
|
||||
|
||||
fireEvent.press(screen.getByText(areaLabel));
|
||||
act(()=>{
|
||||
fireEvent.press(screen.getByText(areaLabel));
|
||||
})
|
||||
const lengthInput = screen.getByLabelText("enter length");
|
||||
const widthInput = screen.getByLabelText("enter length");
|
||||
expect(lengthInput).toBeTruthy();
|
||||
expect(widthInput).toBeTruthy();
|
||||
|
||||
fireEvent.press(screen.getByText("in"));
|
||||
act(() => {
|
||||
fireEvent.press(screen.getByText("in"));
|
||||
})
|
||||
|
||||
act(() => {
|
||||
fireEvent.changeText(lengthInput, "2");
|
||||
@ -59,6 +64,6 @@ describe('ProductCalculatorSelector', () => {
|
||||
const sPrice = price.toLocaleString(undefined, {maximumFractionDigits: 2, minimumFractionDigits: 2,});
|
||||
const element = screen.getByLabelText("calculated price");
|
||||
const {getByText} = within(element);
|
||||
expect(getByText(/\$.*0.*\.10/)).toBeTruthy();
|
||||
expect(getByText(/\$.*15.*\.00/)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
@ -2,21 +2,13 @@ import { renderWithProviders } from "@/lib/rendering";
|
||||
import { ProductEditor } from "@/components/ProductEditor";
|
||||
import { act, fireEvent, screen } from "@testing-library/react-native";
|
||||
import { selectProducts } from "@/features/product/productSlice";
|
||||
import { LumberProduct, Product } from "@/lib/product";
|
||||
import { LumberProduct, Product, productLabel } from "@/lib/product";
|
||||
|
||||
import initialProducts from "@/__fixtures__/initialProducts";
|
||||
|
||||
describe("ProductEditor", () => {
|
||||
const productName = "Flooring";
|
||||
const mockProduct: LumberProduct = {
|
||||
attributes: {
|
||||
name: productName,
|
||||
},
|
||||
pricePerUnit: 10,
|
||||
dimensions: {
|
||||
l: 40,
|
||||
u: "ft",
|
||||
},
|
||||
type: "lumber",
|
||||
};
|
||||
const mockProduct = initialProducts[0];
|
||||
it("renders correctly", async () => {
|
||||
const { store } = renderWithProviders(<ProductEditor />, {
|
||||
products: [mockProduct],
|
||||
@ -30,11 +22,15 @@ describe("ProductEditor", () => {
|
||||
|
||||
// Check if the product names are rendered
|
||||
expect(
|
||||
screen.getByText(products[0].attributes.name as string)
|
||||
screen.getByText(mockProduct.attributes?.name as string)
|
||||
).toBeTruthy();
|
||||
|
||||
const label = productLabel(mockProduct);
|
||||
|
||||
// Start to edit a product
|
||||
fireEvent.press(screen.getByText(productName));
|
||||
act(() => {
|
||||
fireEvent.press(screen.getByText(label));
|
||||
})
|
||||
|
||||
// Change properties of the product to make sure it's updated in the store
|
||||
|
||||
@ -50,7 +46,9 @@ describe("ProductEditor", () => {
|
||||
|
||||
expect(products[0].dimensions.w).toBe(32);
|
||||
|
||||
fireEvent.press(screen.getByLabelText("delete product"));
|
||||
act(() => {
|
||||
fireEvent.press(screen.getByLabelText("delete product"));
|
||||
})
|
||||
products = selectProducts(store.getState());
|
||||
expect(products.length).toBe(0);
|
||||
});
|
||||
|
@ -9,7 +9,7 @@ describe('UnitChooser', () => {
|
||||
|
||||
it('renders correctly', () => {
|
||||
const { getByText } = render(
|
||||
<UnitChooser choices={choices} onChoicePressed={mockOnChoicePressed} />
|
||||
<UnitChooser choices={choices} onUnitSet={mockOnChoicePressed} />
|
||||
);
|
||||
|
||||
choices.forEach(choice => {
|
||||
@ -19,7 +19,7 @@ describe('UnitChooser', () => {
|
||||
|
||||
it('calls onChoicePressed when a button is pressed', () => {
|
||||
const { getByText } = render(
|
||||
<UnitChooser choices={choices} onChoicePressed={mockOnChoicePressed} />
|
||||
<UnitChooser choices={choices} onUnitSet={mockOnChoicePressed} />
|
||||
);
|
||||
|
||||
fireEvent.press(getByText(choices[0]));
|
||||
|
Reference in New Issue
Block a user