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 ( +
+
{icon}
+
+ ); +} + +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 ( +
+ event.preventDefault()} + className="row d-flex justify-content-center" + > +
+ +
+
+ +
+ +
+ ); +} + +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