diff --git a/components/AreaInput.tsx b/components/AreaInput.tsx index 3f6cb07..dab8262 100644 --- a/components/AreaInput.tsx +++ b/components/AreaInput.tsx @@ -1,27 +1,30 @@ -import { View } from "react-native-reanimated/lib/typescript/Animated"; import { MeasurementInput } from "./MeasurementInput"; import { area_t, dimensions_t } from "@/lib/product"; import { useState } from "react"; +import { View } from "react-native"; export type AreaInputProps = { - units: "foot" | "inch", onMeasurementSet?: (area : dimensions_t) => any, + defaultValue?: area_t, + lengthLabel?: string, + widthLabel?: string, } -export function AreaInput({units, onMeasurementSet} : AreaInputProps) { +export function AreaInput({onMeasurementSet, lengthLabel, widthLabel, defaultValue} : AreaInputProps) { - const [area, setArea] = useState({ - l: 0, - w: 0, - u: "foot", - } as area_t) + defaultValue = defaultValue || {l: 0, w: 0, u: "foot"} + + const [area, setArea] = useState(defaultValue) function doOnLengthSet(measurement : dimensions_t) { setArea({ ...area, l: measurement.l }); - onMeasurementSet && onMeasurementSet(area); + onMeasurementSet && onMeasurementSet({ + ...area, + l: measurement.l + }); } function doOnWidthSet(measurement : dimensions_t) { @@ -29,20 +32,25 @@ export function AreaInput({units, onMeasurementSet} : AreaInputProps) { ...area, w: measurement.l }); - onMeasurementSet && onMeasurementSet(area); + onMeasurementSet && onMeasurementSet({ + ...area, + w: measurement.l + }); } return ( ) diff --git a/components/MeasurementInput.tsx b/components/MeasurementInput.tsx index 69deccb..0735b01 100644 --- a/components/MeasurementInput.tsx +++ b/components/MeasurementInput.tsx @@ -1,32 +1,38 @@ import { dimensions_t, length_t } from "@/lib/product"; +import { Length } from "convert"; import { StyleSheet, Text, TextInput, View } from "react-native"; export type t_length_unit = "foot" | "inch" export type MeasurementInputProps = { onValueSet?: (d: dimensions_t) => any, - units: t_length_unit, - defaultValue: number; + defaultValue: length_t; + label?: string, } -export function MeasurementInput({onValueSet, units, defaultValue}: MeasurementInputProps) { - +export function MeasurementInput({onValueSet, defaultValue, label}: MeasurementInputProps) { +1 function doOnValueSet(value : string) { + const iVal = parseFloat(value) || parseInt(value); onValueSet && onValueSet({ - l: (parseInt(value) || parseFloat(value)), - u: units, + ...defaultValue, + l: iVal, }) } + const sDefValue = new String(defaultValue.l).valueOf() + return ( - {units} + style={styles.lengthInput} + aria-label={label || "Enter measurement"} + /> + {defaultValue.u} ) } diff --git a/components/Price.tsx b/components/Price.tsx index e824ff3..d1ac0d4 100644 --- a/components/Price.tsx +++ b/components/Price.tsx @@ -1,5 +1,4 @@ -import { StyleSheet, Text } from "react-native"; -import { View } from "react-native-reanimated/lib/typescript/Animated"; +import { StyleSheet, Text, View } from "react-native"; export type PriceDisplayProps = { price: number, @@ -13,7 +12,7 @@ export default function PriceDisplay({ price }: PriceDisplayProps) { return ( - + $ {price.toLocaleString( undefined, { minimumFractionDigits: 2, diff --git a/components/ProductCalculatorSelector.tsx b/components/ProductCalculatorSelector.tsx index 19707d2..9f6d816 100644 --- a/components/ProductCalculatorSelector.tsx +++ b/components/ProductCalculatorSelector.tsx @@ -8,6 +8,9 @@ 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 { Length } from 'convert'; export default function ProductCalculatorSelector() { @@ -15,7 +18,7 @@ export default function ProductCalculatorSelector() { const products = useAppSelector(selectProducts); const [activeProduct, setActiveProduct] = useState(null as Product | null); const [price, setPrice] = useState(0); - const [measurement, setMeasurement] = useState(null as dimensions_t | null); + const [measurement, setMeasurement] = useState({l: 0, w: 0, u: "ft"} as dimensions_t); useEffect(function () { const iv = setInterval(function () { @@ -33,6 +36,13 @@ export default function ProductCalculatorSelector() { setMeasurement(dimensions); } + function onUnitChosen(unit : Length) { + setMeasurement({ + ...measurement, + u: unit, + }); + } + return ( @@ -42,23 +52,28 @@ export default function ProductCalculatorSelector() { activeProduct ? ( "w" in activeProduct.dimensions ? : ) : ( Please select a product ) } + { + activeProduct && + } - + ); } diff --git a/components/ProductTile.tsx b/components/ProductTile.tsx index 36cc4a5..fb8c9bb 100644 --- a/components/ProductTile.tsx +++ b/components/ProductTile.tsx @@ -33,10 +33,7 @@ export function ProductTile ({product, onProductSelected, isActive, style} : Pro onProductSelected && onProductSelected(product)}> - - {product.attributes.name || `Product ${product.id}`} - ({product.pricePerUnitDisplay}) - + {product.attributes.name || `Product ${product.id}`} ({product.pricePerUnitDisplay}) ); } diff --git a/components/__tests__/AreaInput-test.tsx b/components/__tests__/AreaInput-test.tsx index c7418c1..32f0684 100644 --- a/components/__tests__/AreaInput-test.tsx +++ b/components/__tests__/AreaInput-test.tsx @@ -1,23 +1,31 @@ -import React from 'react'; -import { render, fireEvent } from '@testing-library/react-native'; +import { render, fireEvent, screen } from '@testing-library/react-native'; import { AreaInput } from '../AreaInput'; describe('AreaInput', () => { it('renders correctly', () => { - const { getByPlaceholderText } = render(); - const lengthInput = getByPlaceholderText('Length'); - const widthInput = getByPlaceholderText('Width'); + render(); + const lengthInput = screen.getByLabelText('length'); + const widthInput = screen.getByLabelText('width'); expect(lengthInput).toBeTruthy(); expect(widthInput).toBeTruthy(); }); it('calls onValueSet when a value is entered', () => { - const onValueSetMock = jest.fn(); - const { getByPlaceholderText } = render( - - ); - const lengthInput = getByPlaceholderText('Length'); + const onMeasurementSetMock = jest.fn(); + render(); + const lengthInput = screen.getByLabelText('length'); + const widthInput = screen.getByLabelText('width'); fireEvent.changeText(lengthInput, '10'); - expect(onValueSetMock).toHaveBeenCalledTimes(1); + expect(onMeasurementSetMock).toHaveBeenCalledWith({ + l: 10, + w: 4, + u: "inch" + }); + fireEvent.changeText(widthInput, '10'); + expect(onMeasurementSetMock).toHaveBeenCalledWith({ + l: 10, + w: 10, + u: "inch" + }); }); }); diff --git a/components/__tests__/MeasurementInput-test.tsx b/components/__tests__/MeasurementInput-test.tsx index a69ce1a..0c38bb0 100644 --- a/components/__tests__/MeasurementInput-test.tsx +++ b/components/__tests__/MeasurementInput-test.tsx @@ -1,17 +1,17 @@ -import { render, fireEvent } from '@testing-library/react-native'; +import { render, fireEvent, screen } from '@testing-library/react-native'; import { MeasurementInput } from '../MeasurementInput'; describe('MeasurementInput', () => { it('renders correctly', () => { - const { getByPlaceholderText } = render(); - const input = getByPlaceholderText('Enter measurement'); + render(); + const input = screen.getByLabelText('Enter measurement'); expect(input).toBeTruthy(); }); it('calls onValueSet when value is changed', () => { const mockOnValueSet = jest.fn(); - const { getByPlaceholderText } = render(); - const input = getByPlaceholderText('Enter measurement'); + render(); + const input = screen.getByLabelText('Enter measurement'); fireEvent.changeText(input, '20'); expect(mockOnValueSet).toHaveBeenCalledWith({ l: 20, u: 'foot' }); }); diff --git a/components/__tests__/ProductCalculatorSelector-test.tsx b/components/__tests__/ProductCalculatorSelector-test.tsx index 06248f4..c98aff0 100644 --- a/components/__tests__/ProductCalculatorSelector-test.tsx +++ b/components/__tests__/ProductCalculatorSelector-test.tsx @@ -1,42 +1,70 @@ -import React from 'react'; -import { render, fireEvent } from '@testing-library/react-native'; +import { render, fireEvent, screen, act } from '@testing-library/react-native'; import { Provider } from 'react-redux'; -import { store } from '@/app/store'; import ProductCalculatorSelector from '@/components/ProductCalculatorSelector'; +import { renderWithProviders } from '@/lib/rendering'; +import { Product } from '@/lib/product'; describe('ProductCalculatorSelector', () => { - it('renders correctly', () => { - const { getByText } = render( - - - - ); - expect(getByText('Please select a product')).toBeTruthy(); + const mockAreaProduct = new Product( + 100, + { l: 4, w: 8, u: "ft" }, + {"name": "area product"}, + ); + const mockLengthProduct = new Product( + 100, + { l: 4, u: "ft" }, + {"name": "length product"}, + ); + + it('renders correctly', () => { + renderWithProviders( + (), + { + products: [ + mockAreaProduct.asObject, + mockLengthProduct.asObject, + ], + } + ) + + expect(screen.getByText('Please select a product')).toBeTruthy(); + const label = `${mockAreaProduct.attributes.name} (${mockAreaProduct.pricePerUnitDisplay})`; + expect(screen.getByText(label)).toBeTruthy(); }); - it('updates price when measurement is set', () => { - const { getByTestId, getByText } = render( - - - + it('a product can be selected', () => { + renderWithProviders( + (), + { + products: [ + mockLengthProduct.asObject, + mockAreaProduct.asObject, + ] + } ); - // Assume there is a product with a priceFor function that returns 100 - const product = { - priceFor: jest.fn().mockReturnValue(100), - }; + expect(screen.getByText('Please select a product')).toBeTruthy(); + const areaLabel = `${mockAreaProduct.attributes.name} (${mockAreaProduct.pricePerUnitDisplay})`; + const lengthLabel = `${mockLengthProduct.attributes.name} (${mockLengthProduct.pricePerUnitDisplay})`; - // Assume there are units for measurement - const units = ['cm', 'in']; + fireEvent.press(screen.getByText(areaLabel)); + const lengthInput = screen.getByLabelText("enter length"); + const widthInput = screen.getByLabelText("enter length"); + expect(lengthInput).toBeTruthy(); + expect(widthInput).toBeTruthy(); - // Assume there is a measurement input - const measurementInput = getByTestId('measurement-input'); + fireEvent.press(screen.getByText("in")); - // Simulate user input - fireEvent.changeText(measurementInput, '10'); + act(() => { + fireEvent.changeText(lengthInput, "2"); + fireEvent.changeText(widthInput, "4"); + }); + + jest.advanceTimersByTime(500); - // Check if the price has been updated - expect(getByText('Price: 100')).toBeTruthy(); + const price = mockAreaProduct.priceFor({l: 2, w: 4, u: "ft"}); + const sPrice = price.toLocaleString(undefined, {maximumFractionDigits: 2, minimumFractionDigits: 2,}); + expect(screen.getByLabelText("calculated price").find().toBeTruthy(); }); });