covered product editor test cases.

This commit is contained in:
Jordan 2024-06-27 19:00:29 -07:00
parent 562cae7f5a
commit dc9cfad401
6 changed files with 81 additions and 46 deletions

View File

@ -5,7 +5,7 @@ import { MeasurementInput } from '@/components/LengthInput';
import { setupStore, useAppDispatch } from '../store'; import { setupStore, useAppDispatch } from '../store';
import { selectProducts } from '@/features/product/productSlice'; import { selectProducts } from '@/features/product/productSlice';
import { Product } from '@/lib/product'; import { Product } from '@/lib/product';
import { ProductTile } from '@/components/ProductTyle'; import { ProductTile } from '@/components/ProductTile';
import { Measure, area, length } from 'enheter'; import { Measure, area, length } from 'enheter';
export default function HomeScreen() { export default function HomeScreen() {

View File

@ -1,40 +1,44 @@
import { Product } from "@/lib/product"; import { Product } from "@/lib/product";
import Ionicons from "@expo/vector-icons/Ionicons"; import Ionicons from "@expo/vector-icons/Ionicons";
import React from "react"; import React from "react";
import { ChangeEvent, SetStateAction, useState } from "react"; import { useState } from "react";
import { NativeSyntheticEvent, StyleSheet, Text, TextInput, TextInputChangeEventData, TextInputProps, TouchableHighlight, View } from "react-native"; import { StyleSheet, Text, TextInput, TouchableHighlight, View } from "react-native";
export type ProductAttributeChangeFunc = (product_id: string, key: string, newValue: string) => any; export type ProductAttributeChangeFunc = (product_id: string, key: string, newValue: string) => any;
export type ProductAttributeDeleteFunc = (product_id: string, key: string) => any; export type ProductAttributeDeleteFunc = (product_id: string, key: string) => any;
export type ProductAttributeProps = { product: Product, key: string, value: string, onChange?: ProductAttributeChangeFunc, onDelete?: ProductAttributeChangeFunc, }; export type ProductAttributeProps = { product: Product, attributeKey: string, attributeValue: string, onChange?: ProductAttributeChangeFunc, onDelete?: ProductAttributeChangeFunc, };
export default function ProductAttributeEditor({ product, key, value, onDelete, onChange }: ProductAttributeProps) { export const ProductAttributeEditor = ({ product, attributeKey: key, attributeValue: value, onDelete, onChange } : ProductAttributeProps) => {
const [doEdit, setDoEdit] = useState(true); const [doEdit, setDoEdit] = useState(true);
const [newValue, setNewValue] = useState(value); const [newValue, setNewValue] = useState(value);
const doChange = (e : any) => { const doChange = (e: any) => {
setNewValue(e.target.value); setNewValue(e);
onChange && onChange(product.id, key, e);
} }
return ( return (
<View> <View>
<Text> <Text>{key}</Text>
{key}
</Text>
<View> <View>
<TouchableHighlight
onPress={() => setDoEdit(!doEdit)}
aria-label="Property Value"
>
{doEdit ?
(<Text>{newValue}</Text>) :
(<TextInput
value={newValue}
onChangeText={doChange}
aria-label="Edit Value" />)
}
</TouchableHighlight>
<TouchableHighlight <TouchableHighlight
onPress={() => onDelete && onDelete(product.id, key, value)} onPress={() => onDelete && onDelete(product.id, key, value)}
aria-label="Delete Attribute"> aria-label="Delete Attribute">
<Ionicons name="trash-bin-outline" /> <Ionicons name="trash-bin-outline" />
</TouchableHighlight> </TouchableHighlight>
{doEdit ?
(<Text>
{newValue}
</Text>) : (
<TextInput value={newValue} onChange={doChange} />
)
}
</View> </View>
</View> </View>
) )

View File

@ -5,6 +5,7 @@ import { FlatListComponent, StyleSheet, Text } from "react-native";
import { FlatList } from "react-native-reanimated/lib/typescript/Animated"; import { FlatList } from "react-native-reanimated/lib/typescript/Animated";
import { SafeAreaView } from "react-native-safe-area-context"; import { SafeAreaView } from "react-native-safe-area-context";
import { ProductEditorItem } from "./ProductEditorItem"; import { ProductEditorItem } from "./ProductEditorItem";
import React from "react";
export const ProductEditor = () => { export const ProductEditor = () => {
const products = useAppSelector(selectProducts) as Product []; const products = useAppSelector(selectProducts) as Product [];

View File

@ -1,21 +1,21 @@
import { Product } from "@/lib/product" import { Product } from "@/lib/product"
import { useState } from "react" import { useState } from "react"
import { StyleSheet, Text, TouchableHighlight } from "react-native" import { FlatList, StyleSheet, Text, TouchableHighlight, View } from "react-native"
import { FlatList } from "react-native-reanimated/lib/typescript/Animated"; import {ProductAttributeEditor} from "./ProductAttributeEditor";
import ProductAttributeEditor from "./ProductAttributeEditor"; import React from "react";
export type ProductUpdatedFunc = (product_id : string, product : Product) => any; export type ProductUpdatedFunc = (product_id: string, product: Product) => any;
export type ProductDeletedFunc = (product_id : string) => any; export type ProductDeletedFunc = (product_id: string) => any;
export type ProductEditorItemProps = { export type ProductEditorItemProps = {
product : Product, product: Product,
onProductUpdated?: ProductUpdatedFunc, onProductUpdated?: ProductUpdatedFunc,
onProductDeleted?: ProductDeletedFunc, onProductDeleted?: ProductDeletedFunc,
} }
export const ProductEditorItem = ({ product, onProductUpdated, onProductDeleted }: ProductEditorItemProps) => { export const ProductEditorItem = ({ product, onProductUpdated, onProductDeleted }: ProductEditorItemProps) => {
const [showAttributes, setShowAttributes] = useState(true); const [showAttributes, setShowAttributes] = useState(false);
function onAttributeChanged(product_id: string, key: string, newValue: string) { function onAttributeChanged(product_id: string, key: string, newValue: string) {
product.attributes[key] = newValue; product.attributes[key] = newValue;
@ -25,14 +25,17 @@ export const ProductEditorItem = ({ product, onProductUpdated, onProductDeleted
function onAttributeDelete(product_id: string, key: string) { function onAttributeDelete(product_id: string, key: string) {
product.removeAttribute(key); product.removeAttribute(key);
onProductUpdated && onProductUpdated(product_id, product); onProductDeleted && onProductDeleted(product_id);
} }
return ( return (
<TouchableHighlight <View>
onPress={() => setShowAttributes(true)} <TouchableHighlight
> onPress={() => setShowAttributes(!showAttributes)}
<Text style={styles.product}>{product.attributes.name || `Product ${product.id}`} </Text> aria-label="Product Item"
>
<Text style={styles.product}>{product.attributes.name || `Product ${product.id}`} </Text>
</TouchableHighlight>
{showAttributes && {showAttributes &&
( (
<FlatList <FlatList
@ -40,16 +43,17 @@ export const ProductEditorItem = ({ product, onProductUpdated, onProductDeleted
renderItem={({ item }) => ( renderItem={({ item }) => (
<ProductAttributeEditor <ProductAttributeEditor
product={product} product={product}
key={item.key} attributeKey={item.key || "some key"}
value={item.value} attributeValue={item.value}
onChange={onAttributeChanged} onChange={onAttributeChanged}
onDelete={onAttributeDelete} onDelete={onAttributeDelete}
/> />
)} )}
keyExtractor={(item) => `${product.id}-${item.key}`}
/> />
) )
} }
</TouchableHighlight> </View>
) )
} }

View File

@ -1,27 +1,50 @@
import { Product } from "@/lib/product" import { Product } from "@/lib/product"
import ProductAttributeEditor from "../ProductAttributeEditor" import {ProductAttributeEditor} from "../ProductAttributeEditor"
import { renderWithProviders } from "./util"
import { area } from "enheter" import { area } from "enheter"
import {screen} from '@testing-library/react-native'; import { fireEvent, render, screen } from '@testing-library/react-native';
import React from "react"; import React from "react";
import { emitTypingEvents } from "@testing-library/react-native/build/user-event/type/type";
describe("Product editor tests", () => { describe("Product editor tests", () => {
it("Product attributes render", () => { it("Product attributes can be deleted", async () => {
const product = new Product( const product = new Product(
100, 100,
area("squareFoot", 4 * 7) area("squareFoot", 4 * 7)
); );
const onChange = jest.fn(); const onChange = jest.fn();
const onDelete = jest.fn(); const onDelete = jest.fn();
renderWithProviders( render(
<ProductAttributeEditor <ProductAttributeEditor
key="Name" attributeKey="name"
value="product" attributeValue="product"
product={product} product={product}
onChange={onChange} onChange={onChange}
onDelete={onDelete} onDelete={onDelete}
/>); />);
expect(screen.findByLabelText("Delete Attribute")).not.toBeNull(); 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 productName = "Fun Product";
const product = new Product(
100,
area("squareFoot", 4 * 7),
{ name: productName },
);
const onChange = jest.fn();
const onDelete = jest.fn();
render(
<ProductAttributeEditor
attributeKey="Name"
attributeValue="product"
product={product}
onChange={onChange}
onDelete={onDelete}
/>);
fireEvent.press(screen.getByText("product")); // Use getByText instead of findByText
fireEvent.changeText(screen.getByLabelText("Edit Value"), "new name");
expect(onChange).toHaveBeenCalled();
}) })
}) })

View File

@ -3,5 +3,8 @@ module.exports = {
preset: 'jest-expo', preset: 'jest-expo',
"transformIgnorePatterns": [ "transformIgnorePatterns": [
"node_modules/(?!@ngrx|(?!deck.gl)|ng-dynamic)" "node_modules/(?!@ngrx|(?!deck.gl)|ng-dynamic)"
],
"setupFiles": [
"./setup/jest.js"
] ]
}; };