Skip to content

Commit

Permalink
Chapter 15 updated
Browse files Browse the repository at this point in the history
  • Loading branch information
juhahinkula committed Oct 20, 2023
1 parent 1ce703d commit 44db177
Show file tree
Hide file tree
Showing 15 changed files with 151 additions and 125 deletions.
8 changes: 6 additions & 2 deletions Chapter15/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': 'warn',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
27 changes: 27 additions & 0 deletions Chapter15/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:

- Configure the top-level `parserOptions` property like this:

```js
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
```

- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
4 changes: 2 additions & 2 deletions Chapter15/index.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Carshop</title>
<title>Car Shop</title>
</head>
<body>
<div id="root"></div>
Expand Down
34 changes: 17 additions & 17 deletions Chapter15/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,36 @@
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"test": "vitest"
},
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.11.16",
"@mui/material": "^5.13.6",
"@mui/x-data-grid": "^6.9.0",
"@tanstack/react-query": "^4.29.18",
"axios": "^1.4.0",
"@mui/icons-material": "^5.14.11",
"@mui/material": "^5.14.11",
"@mui/x-data-grid": "^6.15.0",
"@tanstack/react-query": "^4.35.3",
"axios": "^1.5.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/react": "^18.0.37",
"@types/react-dom": "^18.0.11",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"@vitejs/plugin-react": "^4.0.0",
"eslint": "^8.38.0",
"@testing-library/user-event": "^14.5.1",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react": "^4.0.3",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.3.4",
"eslint-plugin-react-refresh": "^0.4.3",
"jsdom": "^22.1.0",
"typescript": "^5.0.2",
"vite": "^4.3.9",
"vitest": "^0.33.0"
"vite": "^4.4.5",
"vitest": "^0.34.6"
}
}
11 changes: 6 additions & 5 deletions Chapter15/src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { describe, test, expect } from 'vitest';
import { render, screen } from "@testing-library/react";
import App from "./App";
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
import App from './App';

describe("App tests", () => {
test("component renders", () => {
render(<App />);
expect(screen.getByText(/Carshop/i)).toBeInTheDocument();
render(<App />);
expect(screen.getByText(/Car Shop/i)).toBeInTheDocument();
})
});
});
20 changes: 10 additions & 10 deletions Chapter15/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ const queryClient = new QueryClient();
function App() {
return (
<Container maxWidth="xl">
<CssBaseline />
<CssBaseline />
<AppBar position="static">
<Toolbar>
<Typography variant="h6">
Carshop
</Typography>
<Typography variant="h6">
Car Shop
</Typography>
</Toolbar>
</AppBar>
<QueryClientProvider client={queryClient}>
<Carlist />
</QueryClientProvider>
</Container>
);
</AppBar>
<QueryClientProvider client={queryClient}>
<Carlist />
</QueryClientProvider>
</Container>
)
}

export default App;
18 changes: 9 additions & 9 deletions Chapter15/src/Carlist.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { describe, test } from 'vitest';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { describe, test, expect } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
import userEvent from '@testing-library/user-event';
import Carlist from './components/Carlist';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -12,28 +13,27 @@ const queryClient = new QueryClient({
},
});

const wrapper = ({ children } : { children: React.ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
const wrapper = ({children } : { children: React.ReactNode }) => (
<QueryClientProvider client = {
queryClient}>{children}
</QueryClientProvider>);

describe("Carlist tests", () => {
test("component renders", () => {
render(<Carlist />, { wrapper });
expect(screen.getByText(/Loading/i)).toBeInTheDocument();
})

test("cars are fetched", async () => {
test("Cars are fetched", async () => {
render(<Carlist />, { wrapper });

await waitFor(() => screen.getByText(/New Car/i));
expect(screen.getByText(/Ford/i)).toBeInTheDocument();
})

test("Open new car modal", async () => {
render(<Carlist />, { wrapper });

await waitFor(() => screen.getByText(/New Car/i));
await userEvent.click(screen.getByText(/New Car/i));
expect(screen.getByText(/Save/i)).toBeInTheDocument();
})
});
});
39 changes: 20 additions & 19 deletions Chapter15/src/components/AddCar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ import { addCar } from '../api/carapi';
import CarDialogContent from './CarDialogContent';

function AddCar() {
const queryClient = useQueryClient();

const [open, setOpen] = useState(false);
const [car, setCar] = useState<Car>({
brand: '',
model: '',
color: '',
registerNumber: '',
registrationNumber: '',
modelYear: 0,
price: 0
});

const queryClient = useQueryClient();

const { mutate } = useMutation(addCar, {
onSuccess: () => {
Expand All @@ -38,29 +38,30 @@ function AddCar() {
setOpen(false);
};

const handleChange = (event : React.ChangeEvent<HTMLInputElement>) => {
setCar({...car, [event.target.name]:
event.target.value});
}

const handleSave = () => {
mutate(car);
setCar({ brand: '', model: '', color: '', registerNumber:'', modelYear: 0, price: 0 });
setCar({ brand: '', model: '', color: '', registrationNumber:'', modelYear: 0, price: 0 });
handleClose();
}

const handleChange = (event : React.ChangeEvent<HTMLInputElement>) => {
setCar({...car, [event.target.name]:
event.target.value});
}


return(
<>
<Button onClick={handleClickOpen}>New Car</Button>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>New car</DialogTitle>
<CarDialogContent car={car} handleChange={handleChange}/>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button onClick={handleSave}>Save</Button>
</DialogActions>
</Dialog>
<Button onClick={handleClickOpen}>New Car</Button>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>New car</DialogTitle>
<CarDialogContent car={car} handleChange={handleChange} />
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button onClick={handleSave}>Save</Button>
</DialogActions>
</Dialog>
</>
);
}

export default AddCar;
18 changes: 8 additions & 10 deletions Chapter15/src/components/CarDialogContent.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Car } from '../types';
import DialogContent from '@mui/material/DialogContent';
import TextField from '@mui/material/TextField';
import Stack from '@mui/material/Stack';
import DialogContent from '@mui/material/DialogContent';
import { Car } from '../types';

type DialogFormProps = {
car: Car;
Expand All @@ -10,24 +10,22 @@ type DialogFormProps = {

function CarDialogContent({ car, handleChange }: DialogFormProps) {
return (
<>
<DialogContent>
<Stack spacing={2} mt={1}>
<TextField label="Brand" name="brand"
value={car.brand} onChange={handleChange} fullWidth/>
value={car.brand} onChange={handleChange}/>
<TextField label="Model" name="model"
value={car.model} onChange={handleChange} fullWidth/>
value={car.model} onChange={handleChange}/>
<TextField label="Color" name="color"
value={car.color} onChange={handleChange} fullWidth/>
value={car.color} onChange={handleChange}/>
<TextField label="Year" name="modelYear"
value={car.modelYear} onChange={handleChange} fullWidth/>
value={car.modelYear} onChange={handleChange}/>
<TextField label="Reg.nr." name="registerNumber"
value={car.registerNumber} onChange={handleChange} fullWidth/>
value={car.registrationNumber} onChange={handleChange}/>
<TextField label="Price" name="price"
value={car.price} onChange={handleChange} fullWidth/>
value={car.price} onChange={handleChange}/>
</Stack>
</DialogContent>
</>
);
}

Expand Down
26 changes: 14 additions & 12 deletions Chapter15/src/components/Carlist.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { getCars, deleteCar } from '../api/carapi';
import { DataGrid, GridColDef, GridCellParams, GridToolbar } from '@mui/x-data-grid';
import Snackbar from '@mui/material/Snackbar';
import IconButton from '@mui/material/IconButton';
import DeleteIcon from '@mui/icons-material/Delete';
import AddCar from './AddCar';
import EditCar from './EditCar';
import { getCars, deleteCar } from '../api/carapi';

function Carlist() {
const [open, setOpen] = useState(false);

const [open, setOpen] = useState(false);
const queryClient = useQueryClient();

const { data, error, isSuccess } = useQuery({
Expand All @@ -27,12 +27,12 @@ function Carlist() {
console.error(err);
},
});

const columns: GridColDef[] = [
{field: 'brand', headerName: 'Brand', width: 200},
{field: 'model', headerName: 'Model', width: 200},
{field: 'color', headerName: 'Color', width: 200},
{field: 'registerNumber', headerName: 'Reg.nr.', width: 150},
{field: 'registrationNumber', headerName: 'Reg.nr.', width: 150},
{field: 'modelYear', headerName: 'Model Year', width: 150},
{field: 'price', headerName: 'Price', width: 150},
{
Expand All @@ -53,17 +53,19 @@ function Carlist() {
filterable: false,
disableColumnMenu: true,
renderCell: (params: GridCellParams) => (
<IconButton aria-label='delete' size="small"
onClick={() => {
if (window.confirm("Are you sure to delete?"))
mutate(params.row._links.car.href)
}}>
<IconButton aria-label="delete" size="small"
onClick={() => {
if (window.confirm(`Are you sure you want to delete ${params.row.brand} ${params.row.model}?`))
mutate(params.row._links.car.href)
}}
>
<DeleteIcon fontSize="small" />
</IconButton>

),
},
];

];
if (!isSuccess) {
return <span>Loading...</span>
}
Expand Down
Loading

0 comments on commit 44db177

Please sign in to comment.