diff --git a/husan-weather-map/.gitignore b/husan-weather-map/.gitignore
new file mode 100644
index 0000000..4d29575
--- /dev/null
+++ b/husan-weather-map/.gitignore
@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/husan-weather-map/README.md b/husan-weather-map/README.md
new file mode 100644
index 0000000..cafdf09
--- /dev/null
+++ b/husan-weather-map/README.md
@@ -0,0 +1,3 @@
+# OpenWeather Map
+
+Bu dastur orqali siz istalgan shaharning ayni vaqtdagi ob-havo ma'lumotini olishingiz mumkin
\ No newline at end of file
diff --git a/husan-weather-map/package.json b/husan-weather-map/package.json
new file mode 100644
index 0000000..5696da0
--- /dev/null
+++ b/husan-weather-map/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "weather-map-2",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "@testing-library/jest-dom": "^5.11.4",
+ "@testing-library/react": "^11.1.0",
+ "@testing-library/user-event": "^12.1.10",
+ "bootstrap": "4.6.0",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2",
+ "react-icons": "^4.2.0",
+ "react-scripts": "4.0.3",
+ "web-vitals": "^1.0.1"
+ },
+ "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": {
+ "react-test-renderer": "^17.0.2"
+ }
+}
diff --git a/husan-weather-map/public/favicon.ico b/husan-weather-map/public/favicon.ico
new file mode 100644
index 0000000..a11777c
Binary files /dev/null and b/husan-weather-map/public/favicon.ico differ
diff --git a/husan-weather-map/public/index.html b/husan-weather-map/public/index.html
new file mode 100644
index 0000000..103b125
--- /dev/null
+++ b/husan-weather-map/public/index.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ OpenWeather Map
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/husan-weather-map/public/logo192.png b/husan-weather-map/public/logo192.png
new file mode 100644
index 0000000..fc44b0a
Binary files /dev/null and b/husan-weather-map/public/logo192.png differ
diff --git a/husan-weather-map/public/logo512.png b/husan-weather-map/public/logo512.png
new file mode 100644
index 0000000..a4e47a6
Binary files /dev/null and b/husan-weather-map/public/logo512.png differ
diff --git a/husan-weather-map/public/manifest.json b/husan-weather-map/public/manifest.json
new file mode 100644
index 0000000..080d6c7
--- /dev/null
+++ b/husan-weather-map/public/manifest.json
@@ -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"
+}
diff --git a/husan-weather-map/public/robots.txt b/husan-weather-map/public/robots.txt
new file mode 100644
index 0000000..e9e57dc
--- /dev/null
+++ b/husan-weather-map/public/robots.txt
@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:
diff --git a/husan-weather-map/src/Components/App.js b/husan-weather-map/src/Components/App.js
new file mode 100644
index 0000000..e0126a3
--- /dev/null
+++ b/husan-weather-map/src/Components/App.js
@@ -0,0 +1,22 @@
+import React, { useState } from 'react';
+import Header from './Header';
+import Form from './Form';
+import Response from './Response';
+
+function App() {
+ const [cityName, setCityName] = useState(null);
+
+ function handleSubmit(cityName) {
+ setCityName(cityName);
+ }
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default App;
\ No newline at end of file
diff --git a/husan-weather-map/src/Components/DesIcon.js b/husan-weather-map/src/Components/DesIcon.js
new file mode 100644
index 0000000..28570ae
--- /dev/null
+++ b/husan-weather-map/src/Components/DesIcon.js
@@ -0,0 +1,52 @@
+import React, { useState, useEffect } from 'react';
+import ModerateRain from './Icons/ModerateRain';
+import Rain from './Icons/Rain';
+import Cloud from './Icons/Cloud';
+import ClearSky from './Icons/ClearSky';
+import Sunny from './Icons/Sunny';
+import PropTypes from 'prop-types';
+
+DesIcon.propTypes = {
+ Description: PropTypes.string,
+}
+
+function DesIcon(props) {
+ const [icon, setIcon] = useState(null);
+ const [description, setDescription] = useState(null);
+
+ useEffect(() => {
+ setDescription(props.Description);
+ }, [props.Description]);
+
+ useEffect(() => {
+ if (description !== null) {
+ if (description.includes('sky')) {
+ setIcon();
+ }
+
+ if (description.includes('light rain')) {
+ setIcon();
+ }
+
+ if (description.includes('rain') && !description.includes('light')) {
+ setIcon();
+ }
+
+ if (description.includes('sunny')) {
+ setIcon();
+ }
+
+ if (description.includes('cloud')) {
+ setIcon();
+ }
+ }
+ }, [description]);
+
+ return (
+
+ );
+}
+
+export default DesIcon;
\ No newline at end of file
diff --git a/husan-weather-map/src/Components/Form.js b/husan-weather-map/src/Components/Form.js
new file mode 100644
index 0000000..2c61c6e
--- /dev/null
+++ b/husan-weather-map/src/Components/Form.js
@@ -0,0 +1,51 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+
+Form.propTypes = {
+ handleSubmit: PropTypes.func,
+}
+
+function Form(props) {
+ const [inputValue, setInputValue] = useState('');
+
+ const handleChange = ({ target }) => {
+ const { value } = target;
+ setInputValue(value);
+ }
+
+ const submitForm = () => {
+ props.handleSubmit(inputValue);
+ }
+
+ return (
+
+ );
+}
+
+export default Form;
\ No newline at end of file
diff --git a/husan-weather-map/src/Components/Header.js b/husan-weather-map/src/Components/Header.js
new file mode 100644
index 0000000..c9bbac2
--- /dev/null
+++ b/husan-weather-map/src/Components/Header.js
@@ -0,0 +1,11 @@
+import React from 'react';
+
+function Header() {
+ return (
+
+
OpenWeather Map
+
+ );
+}
+
+export default Header;
\ No newline at end of file
diff --git a/husan-weather-map/src/Components/Icons/ClearSky.js b/husan-weather-map/src/Components/Icons/ClearSky.js
new file mode 100644
index 0000000..ec17cd7
--- /dev/null
+++ b/husan-weather-map/src/Components/Icons/ClearSky.js
@@ -0,0 +1,8 @@
+import React from 'react';
+import { WiDayCloudy } from 'react-icons/wi';
+
+function ClearSky() {
+ return
;
+}
+
+export default ClearSky;
\ No newline at end of file
diff --git a/husan-weather-map/src/Components/Icons/Cloud.js b/husan-weather-map/src/Components/Icons/Cloud.js
new file mode 100644
index 0000000..9de5dfe
--- /dev/null
+++ b/husan-weather-map/src/Components/Icons/Cloud.js
@@ -0,0 +1,8 @@
+import React from 'react';
+import { WiCloudy } from 'react-icons/wi';
+
+function Cloud() {
+ return ;
+}
+
+export default Cloud;
\ No newline at end of file
diff --git a/husan-weather-map/src/Components/Icons/ModerateRain.js b/husan-weather-map/src/Components/Icons/ModerateRain.js
new file mode 100644
index 0000000..a2c7a9d
--- /dev/null
+++ b/husan-weather-map/src/Components/Icons/ModerateRain.js
@@ -0,0 +1,8 @@
+import React from 'react';
+import { WiDayRainMix } from 'react-icons/wi';
+
+function ModerateRain() {
+ return
;
+}
+
+export default ModerateRain;
\ No newline at end of file
diff --git a/husan-weather-map/src/Components/Icons/Rain.js b/husan-weather-map/src/Components/Icons/Rain.js
new file mode 100644
index 0000000..ba378db
--- /dev/null
+++ b/husan-weather-map/src/Components/Icons/Rain.js
@@ -0,0 +1,8 @@
+import React from 'react';
+import { WiRainMix } from 'react-icons/wi';
+
+function Rain() {
+ return ;
+}
+
+export default Rain;
\ No newline at end of file
diff --git a/husan-weather-map/src/Components/Icons/Sunny.js b/husan-weather-map/src/Components/Icons/Sunny.js
new file mode 100644
index 0000000..2601f9f
--- /dev/null
+++ b/husan-weather-map/src/Components/Icons/Sunny.js
@@ -0,0 +1,8 @@
+import React from 'react';
+import { MdWbSunny } from 'react-icons/md';
+
+function Sunny() {
+ return ;
+}
+
+export default Sunny;
\ No newline at end of file
diff --git a/husan-weather-map/src/Components/Response.js b/husan-weather-map/src/Components/Response.js
new file mode 100644
index 0000000..f136896
--- /dev/null
+++ b/husan-weather-map/src/Components/Response.js
@@ -0,0 +1,51 @@
+import React, { useState, useEffect } from 'react';
+import PropTypes from 'prop-types';
+import DesIcon from './DesIcon';
+
+Response.propTypes = {
+ cityName: PropTypes.string,
+}
+
+function Response(props) {
+ const [temp, setTemp] = useState(null);
+ const [description, setDescription] = useState(null);
+ const [cityName, setCityName] = useState(null);
+
+ const name = props.cityName;
+ const url = 'https://api.openweathermap.org/data/2.5/weather?';
+ const apiKey = '2d60f525cd75dd81b166855758cb0334';
+ const urlToFetch = `${url}q=${name}&appid=${apiKey}`;
+
+ async function getWeatherInfo(url) {
+ try {
+ const response = await fetch(url);
+ if (response.ok) {
+ const jsonResponse = await response.json();
+ const fixedTemp = Math.round(jsonResponse.main.temp - 273.15);
+ setTemp(fixedTemp + '°C');
+ setDescription(jsonResponse.weather[0].description);
+ setCityName(jsonResponse.name);
+ }
+ else {
+ throw new Error('Request Failed!');
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ }
+
+ useEffect(() => getWeatherInfo(urlToFetch));
+
+ return (
+
+
+
{cityName}
+ {temp}
+ {description}
+
+
+
+ );
+}
+
+export default Response;
\ No newline at end of file
diff --git a/husan-weather-map/src/Tests/Form.test.js b/husan-weather-map/src/Tests/Form.test.js
new file mode 100644
index 0000000..c3b2cbf
--- /dev/null
+++ b/husan-weather-map/src/Tests/Form.test.js
@@ -0,0 +1,44 @@
+import Form from '../Components/Form';
+import { render, screen, fireEvent } from '@testing-library/react';
+
+describe('', () => {
+ test("input's initial value should be empty", () => {
+ render();
+ const input = screen.getByRole('textbox');
+
+ expect(input.value).toBe('');
+ });
+
+ test("input's value should update correctly", () => {
+ render();
+ const input = screen.getByRole('textbox');
+
+ fireEvent.change(input, { target: { value: 'tashkent' } });
+
+ expect(input.value).toBe('tashkent');
+ });
+
+
+ test('handleSubmit function should be called', () => {
+ const onSubmit = jest.fn();
+ render();
+ const button = screen.getByRole('button');
+
+ fireEvent.click(button);
+
+ expect(onSubmit).toHaveBeenCalled();
+ });
+
+ test('handleSubmit function should be called when input\'s value updates', () => {
+ const onSubmit = jest.fn();
+ render();
+
+ const input = screen.getByRole('textbox');
+ const button = screen.getByRole('button');
+
+ fireEvent.change(input, { target: { value: '2032' } });
+ fireEvent.click(button);
+
+ expect(onSubmit).toHaveBeenCalled();
+ });
+});
\ No newline at end of file
diff --git a/husan-weather-map/src/Tests/Header.test.js b/husan-weather-map/src/Tests/Header.test.js
new file mode 100644
index 0000000..ca82a4b
--- /dev/null
+++ b/husan-weather-map/src/Tests/Header.test.js
@@ -0,0 +1,18 @@
+import Header from '../Components/Header';
+import { render, screen } from '@testing-library/react';
+
+describe('', () => {
+ test('heading should not be empty', () => {
+ render();
+ const heading = screen.getByTestId('custom-element');
+
+ expect(heading.textContent).not.toHaveLength(0);
+ });
+
+ test('heading should the same excatly text', () => {
+ render();
+ const heading = screen.getByTestId('custom-element');
+
+ expect(heading.textContent).toBe('OpenWeather Map');
+ });
+});
\ No newline at end of file
diff --git a/husan-weather-map/src/Tests/Response.test.js b/husan-weather-map/src/Tests/Response.test.js
new file mode 100644
index 0000000..8aac154
--- /dev/null
+++ b/husan-weather-map/src/Tests/Response.test.js
@@ -0,0 +1,54 @@
+import Response from '../Components/Response';
+import { render, screen, act } from '@testing-library/react';
+
+describe('', () => {
+ test('Response component should mount', () => {
+ render();
+ });
+
+ test('fetch function works correctly', async () => {
+ global.fetch = jest.fn(() => Promise.resolve({
+ ok: true,
+ json: () => Promise.resolve({
+ main: {
+ temp: 285.15,
+ },
+ weather: [
+ {
+ description: 'sunny',
+ },
+ ],
+ name: 'Tashkent',
+ }),
+ }));
+
+ await act(async () => render());
+ expect(screen.getByTestId('weather-info-div').textContent).toBe('Tashkent12°Csunny');
+ });
+
+ test('fetch function works correctly', async () => {
+ global.fetch = jest.fn(() => Promise.resolve({
+ ok: true,
+ json: () => Promise.resolve({
+ main: {
+ temp: 268.15,
+ },
+ weather: [
+ {
+ description: 'rainy',
+ },
+ ],
+ name: 'Moscow',
+ }),
+ }));
+
+ await act(async () => render());
+ expect(screen.getByTestId('weather-info-div').textContent).toBe('Moscow-5°Crainy');
+ });
+
+ test('if fetch function returns bad response', async () => {
+ await act(async () => render());
+
+ expect(screen.getByTestId('weather-info-div').textContent).toBe('');
+ });
+});
\ No newline at end of file
diff --git a/husan-weather-map/src/index.css b/husan-weather-map/src/index.css
new file mode 100644
index 0000000..85b1b35
--- /dev/null
+++ b/husan-weather-map/src/index.css
@@ -0,0 +1,23 @@
+#app-width {
+ width: 60%;
+}
+
+#form-btn {
+
+}
+
+@media (min-width: 426px) and (max-width: 768px) {
+ #app-width {
+ width: 80%;
+ }
+}
+
+@media (max-width: 425px) {
+ #app-width {
+ width: 100%;
+ }
+
+ #form-btn {
+ margin-top: 10px;
+ }
+}
diff --git a/husan-weather-map/src/index.js b/husan-weather-map/src/index.js
new file mode 100644
index 0000000..3b21420
--- /dev/null
+++ b/husan-weather-map/src/index.js
@@ -0,0 +1,6 @@
+import ReactDOM from 'react-dom';
+import App from './Components/App';
+import 'bootstrap/dist/css/bootstrap.css';
+import './index.css';
+
+ReactDOM.render(, document.getElementById('root'));
\ No newline at end of file