This commit is contained in:
Kenneth Jannette
2023-04-17 01:46:23 -05:00
commit d0405a00eb
26 changed files with 31407 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.DS_STORE
node_modules
mgk.js

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"editor.formatOnSave": true
}

21
README.md Normal file
View File

@@ -0,0 +1,21 @@
# Getting Started with the Vechile Database App
## Initial Setup
1. Unzip project in your local working directory
2. Install dependencies by running command: npm install
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app locally in the development mode for testing.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.

30601
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

42
package.json Normal file
View File

@@ -0,0 +1,42 @@
{
"name": "hdepot",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"firebase": "^9.13.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@testing-library/dom": "^8.19.0",
"@testing-library/user-event": "^14.4.3"
}
}

43
public/index.html Normal file
View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Vehicle App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

25
public/manifest.json Normal file
View File

@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
public/robots.txt Normal file
View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

15
src/App.js Normal file
View File

@@ -0,0 +1,15 @@
import React from "react";
import "./styles/App.css";
import Vehicles from "./components/Vehicles";
import Navbar from "./components/Navbar";
const App = () => {
return (
<div className="app-container">
<Navbar />
<Vehicles />
</div>
);
};
export default App;

16
src/components/Navbar.js Normal file
View File

@@ -0,0 +1,16 @@
import React from "react";
const Navbar = (props) => {
return (
<div className="navBarContainer">
<div className="navRow">
<span className="navSpan">Home</span>
<span className="navSpan">Edit Profile</span>
<span className="navSpan">Edit Settings</span>
<span className="navSpan">New Request</span>
</div>
</div>
);
};
export default Navbar;

74
src/components/Vehicle.js Normal file
View File

@@ -0,0 +1,74 @@
import React from "react";
import { formatNums } from "../utilities/utilities";
import Button from "../pageElements/button";
const Vehicle = (props) => {
const { make, model, trim, color, year, category, mileage, price, id } =
props;
return (
<div className="veh-container">
<div className="vehBox">
<div className="veh-col">
<div className="veh-row">
<p className="veh-text">
<b>Make: </b>
{make}
</p>
<p className="veh-text">
<b>Model: </b>
{model}
</p>
<p className="veh-text">
<b>Trim: </b>
{trim}
</p>
<p className="veh-text">
<b>Color: </b>
{color}
</p>
</div>
</div>
<div className="veh-col">
<div className="veh-row">
<p className="veh-text">
<b>Year: </b>
{year}
</p>
<p className="veh-text">
<b>Category: </b>
{category}
</p>
<p className="veh-text">
<b>Mileage: </b>
{formatNums(mileage)}
</p>
</div>
</div>
<div className="veh-col">
<div className="veh-row-price">
<p className="veh-text">
<b>Price: </b>
{`$${formatNums(price / 100)}.00`}
</p>
<p className="veh-text">
<b>Id: </b>
{id}
</p>
</div>
</div>
</div>
<div className="deleteButtonBox">
<Button
className="deleteButton"
labelText="Delete Vehicle"
variant="contained"
color="primary"
onClick={() => props.onClick(props.id)}
/>
</div>
</div>
);
};
export default Vehicle;

212
src/components/Vehicles.js Normal file
View File

@@ -0,0 +1,212 @@
import React, { useState, useEffect } from "react";
import { db } from "../firebase.js";
import {
collection,
getDoc,
deleteDoc,
onSnapshot,
doc,
addDoc,
} from "firebase/firestore";
import Vehicle from "./Vehicle";
import TextInput from "../pageElements/textInput";
import Button from "../pageElements/button";
const Vehicles = () => {
const [savedVehicles, setSavedVehicles] = useState([]);
const [inputs, setInputs] = useState({});
const [vehId, setVehId] = useState();
const [fetchedVeh, setfetchedVeh] = useState([]);
const inputTypes = [
"make",
"model",
"trim",
"color",
"year",
"category",
"mileage",
"price",
];
const handleIdInput = (e) => setVehId(e.target.value);
const handleChange = (e) => {
setInputs((prevState) => ({
...prevState,
[e.target.name]: e.target.value,
}));
};
const handleNumChange = (e) => {
const value = e.target.value;
const formattedValue = (
Number(value.replace(/\D/g, "")) || ""
).toLocaleString();
setInputs((prevState) => ({
...prevState,
[e.target.name]: formattedValue,
}));
};
const format = (e) => {
e.preventDefault();
const formatMiles = parseFloat(inputs.mileage.replace(/,/g, ""));
const formatPrice = parseFloat(inputs.price.replace(/,/g, "") * 100);
inputs.mileage = formatMiles;
inputs.price = formatPrice;
};
useEffect(() => {
try {
onSnapshot(collection(db, "hdepot"), (snapshot) => {
const data = snapshot.docs.map((doc) => doc.data());
if (data) {
const ids = snapshot.docs.map((doc) => doc.id);
const dbVehs = data.map((item, i) => {
return Object.assign(item, { id: ids[i] });
});
setSavedVehicles(dbVehs);
} else {
console.log("Error fetching vehicle data from database");
}
});
} catch (error) {
console.log(`A system error has occurred: ${error}`);
}
}, []);
async function getOneVehicle() {
const docRef = doc(db, "hdepot", `${vehId}`);
try {
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
const newcar = docSnap.data();
newcar.id = docSnap._key.path.segments[1];
setfetchedVeh([newcar]);
} else {
console.log("DB item does not exist");
}
} catch (error) {
console.log(`A system error has occurred: ${error}`);
}
setVehId("");
}
async function addData(e) {
e.preventDefault();
format(e);
setSavedVehicles([...savedVehicles, inputs]);
try {
const docRef = await addDoc(collection(db, "hdepot"), inputs);
inputs.id = docRef.id;
setSavedVehicles([...savedVehicles, inputs]);
setInputs({});
} catch (error) {
console.log(`Error saving vehicle to db: ${error}`);
}
}
async function deleteData(e) {
try {
deleteDoc(doc(db, "hdepot", e));
setfetchedVeh("");
} catch (error) {
console.log(`Error deleteing vehicle from db: ${error}`);
}
}
return (
<>
<div className="vehicles-container">
<h2 className="formHeader">Add A Vehicle to Your Queue</h2>
<div className="vehiclesRow">
<div className="vehiclesColumn">
{inputTypes.map((type, i) => (
<div className="inputContainer">
<TextInput
key={`${type}${i}`}
className="text-input"
name={type}
placeholder={type}
value={inputs[type] || ""}
onChange={
type === "mileage" || type === "price"
? handleNumChange
: handleChange
}
/>
</div>
))}
</div>
</div>
<Button
className="auxButton"
labelText="Add Vehicle"
variant="contained"
color="primary"
onClick={addData}
/>
<div>
{savedVehicles && (
<h2 className="formHeader"> Your Saved Vehicles</h2>
)}
{savedVehicles.map((el, i) => (
<Vehicle
key={`${el.model}${i}`}
make={el.make}
model={el.model}
trim={el.trim}
color={el.color}
category={el.category}
mileage={el.mileage}
price={el.price}
year={el.year}
id={el.id}
onClick={deleteData}
/>
))}
</div>
<div className="getHeader">
<h2 className="formHeader">Get A Vehicle By Id</h2>
</div>
<div className="fetchBox">
<div className="inputContainer">
<TextInput
className="text-input"
name="vehId"
placeholder="Vehicle Id"
value={vehId}
onChange={handleIdInput}
/>
</div>
<Button
className="auxButton"
labelText="Get Vehicle"
variant="contained"
color="primary"
onClick={getOneVehicle}
/>
</div>
<div>
{fetchedVeh &&
fetchedVeh.map((el, i) => (
<Vehicle
make={el.make}
model={el.model}
key={`${el.model}${i}`}
trim={el.trim}
color={el.color}
year={el.year}
category={el.category}
mileage={el.mileage}
price={el.price}
id={el.id}
onClick={deleteData}
/>
))}
</div>
</div>
</>
);
};
export default Vehicles;

25
src/firebase.js Normal file
View File

@@ -0,0 +1,25 @@
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import {
FIREBASE_API_KEY,
FIREBASE_AUTH_DOMAIN,
FIREBASE_PROJECT_ID,
FIREBASE_STORAGE_BUCKET,
FIREBASE_MESSAGING_SENDER_ID,
FIREBASE_APP_ID,
FIREBASE_MEASUREMENT_ID,
} from "./mgk";
const firebaseConfig = {
apiKey: FIREBASE_API_KEY,
authDomain: FIREBASE_AUTH_DOMAIN,
projectId: FIREBASE_PROJECT_ID,
storageBucket: FIREBASE_STORAGE_BUCKET,
messagingSenderId: FIREBASE_MESSAGING_SENDER_ID,
appId: FIREBASE_APP_ID,
measurementId: FIREBASE_MEASUREMENT_ID,
};
const firebaseApp = initializeApp(firebaseConfig);
const db = getFirestore(firebaseApp);
export { db };

13
src/index.css Normal file
View File

@@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

18
src/index.js Normal file
View File

@@ -0,0 +1,18 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

1
src/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,17 @@
import React from 'react';
const Button = (props) => {
const { className, variant, color, onClick, labelText } = props
return (
<button
className={className}
variant={variant}
color={color}
onClick={onClick}
>
{labelText}
</button>
)
}
export default Button;

View File

@@ -0,0 +1,16 @@
import React from 'react';
const TextInput = (props) => {
const { className, name, placeholder, value, onChange } = props
return (
<input
className={className}
name={name}
placeholder={placeholder}
value={value}
onChange={onChange}
/>
)
}
export default TextInput

13
src/reportWebVitals.js Normal file
View File

@@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

5
src/setupTests.js Normal file
View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

180
src/styles/App.css Normal file
View File

@@ -0,0 +1,180 @@
@import url("https://fonts.googleapis.com/css?family=Open+Sans");
body {
font-family: "Open Sans", sans-serif;
}
.app-container {
background-color: #f5f5f5;
min-height: 100vh;
display: flex;
flex-direction: column;
font-size: 16px;
}
.vehicles-container {
display: flex;
flex-direction: column;
width: 100%;
padding: 0px 30px;
margin: 5px 20px;
}
.vehiclesRow {
display: flex;
flex-direction: row;
width: 100%;
}
.vehiclesColumn {
display: flex;
flex-direction: column;
flex-wrap: wrap;
height: 205px;
}
.veh-col {
display: flex;
flex-direction: column;
}
.veh-row {
display: flex;
flex-direction: row;
}
.veh-row-price {
display: flex;
flex-direction: row;
}
.vehBox {
padding: 5px 5px 8px 8px;
border: 1px solid #5d86a8;
border-radius: 6px;
max-width: 535px;
flex: 1 0 auto;
}
.veh-text {
margin: 4px 5px;
}
.formHeader {
letter-spacing: 0.08rem;
}
.text-input {
width: 100%;
height: 24px;
margin: 8px 0px;
border: 1px solid #c0c8cf;
border-radius: 4px;
}
.auxButton {
margin-top: 8px;
height: 30px;
width: 125px;
border: 1px solid #c0c8cf;
border-radius: 6px;
background-color: white;
color: #0f1111;
font-family: "Open Sans", sans-serif;
cursor: pointer;
padding: 7px 0px 26px 0px;
}
.button {
margin-top: 8px;
align-items: center;
height: 30px;
width: 125px;
border: 1px solid #c0c8cf;
border-radius: 6px;
background-color: white;
color: #0f1111;
font-family: "Open Sans", sans-serif;
cursor: pointer;
padding: 7px 0px 16px 0px;
}
.deleteButton {
margin-top: 8px;
height: 30px;
width: 125px;
border: 1px solid #c0c8cf;
border-radius: 6px;
background-color: white;
color: #0f1111;
cursor: pointer;
line-height: 29px;
font-family: "Open Sans", sans-serif;
padding: 2px 0px 31px 0px;
vertical-align: middle;
margin-top: 12px;
}
.deleteButtonBox {
margin: 15px 0px 20px 0px;
}
.inputContainer {
min-width: 250px;
max-width: 250px;
margin-right: 25px;
padding-bottom: 5px;
padding-right: 5px;
}
.navBarContainer {
width: 100%;
height: 50px;
padding: 15px 20px 0px 20px;
background-color: white;
border-bottom: 2px solid #a9a9a9;
}
.navRow {
margin: 0px 15px 2px 15px;
padding: 5px 5px;
font-size: 1rem;
font-family: "Open Sans", sans-serif;
letter-spacing: 0.05rem;
}
.navSpan {
margin: 10px 10px;
}
.getHeader {
margin-top: -8px;
}
.fetchBox {
margin-bottom: 25px;
}
@media only screen and (max-width: 460px) {
.vehiclesColumn {
height: 100%;
}
.navBarContainer {
display: none;
}
.formHeader {
max-width: 250px;
}
.vehBox {
max-width: 260px;
display: flex;
flex-wrap: wrap;
}
.veh-row-price {
display: flex;
flex-direction: column;
}
}

7
src/tests/App.test.js Normal file
View File

@@ -0,0 +1,7 @@
import { render } from '@testing-library/react';
import App from '../App';
// Test render of top-level App compoennt
test('renders the App', () => {
render(<App />);
});

View File

@@ -0,0 +1,22 @@
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event'
import Vehicles from '../../../components/Vehicles'
// Test renders a child compoennt
test('Renders the Vehicles Component And Its Elements', () => {
const vehicleComponent = render(<Vehicles />)
const allHeadings = screen.getAllByRole("heading");
expect(allHeadings).toHaveLength(3);
const allInputs = screen.getAllByRole("textbox");
expect(allInputs).toHaveLength(9);
const allButtons = screen.getAllByRole("button");
expect(allButtons).toHaveLength(2);
const someHeadingText = screen.getByText(/Add New Vehicle/i);
expect(someHeadingText).toBeInTheDocument();
const somebuttonText = screen.getByText(/Add Vehicle/i);
expect(somebuttonText).toBeInTheDocument();
});

View File

@@ -0,0 +1,12 @@
import { render, screen, fireEvent } from '@testing-library/react';
import Button from '../../../pageElements/Button'
// Tests render of a button and its dynamic behavior
test('Button renders with label text and clicks', async () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick} labelText="Add Vehicle"/>)
const somebuttonText = screen.getByText(/Add Vehicle/i);
await expect(somebuttonText).toBeInTheDocument();
fireEvent.click(screen.getByText(/Add Vehicle/i))
expect(handleClick).toHaveBeenCalledTimes(1)
});

View File

@@ -0,0 +1,12 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'
import TextInput from '../../../pageElements/textInput';
// Tests render of a text input and its dynamic behavior
test('TextInoput renders with placeolder text and diplays inputted values', async () => {
render(<TextInput placeholder="Model" />)
const makeInput = screen.getByPlaceholderText("Model");
await userEvent.type(makeInput, "Ford");
expect(makeInput.value).toBe("Ford")
});

View File

@@ -0,0 +1,8 @@
export const formatNums = (x) => {
if (x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
} else {
return ''
}
}