Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
winolog authored and winolog committed Dec 24, 2024
2 parents 19fdd51 + 0797e41 commit 4cb2177
Show file tree
Hide file tree
Showing 40 changed files with 1,096 additions and 12 deletions.
29 changes: 17 additions & 12 deletions 01-basics/20-weather/WeatherApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,50 @@ import { getWeatherData, WeatherConditionIcons } from './weather.service.ts'

export default defineComponent({
name: 'WeatherApp',

setup() {
return {
weatherData: getWeatherData(),
weatherConditionIcons: WeatherConditionIcons
}
},
template: `
<div>
<h1 class="title">Погода в Средиземье</h1>
<ul class="weather-list unstyled-list">
<li class="weather-card weather-card--night">
<div class="weather-alert">
<ul class="weather-list unstyled-list" v-for="item in weatherData">
<li class="weather-card" :class="{'weather-card--night': item.current.dt < item.current.sunrise || item.current.dt > item.current.sunset}" >
<div class="weather-alert" v-if="item.alert">
<span class="weather-alert__icon">⚠️</span>
<span class="weather-alert__description">Королевская метеослужба короля Арагорна II: Предвещается наступление сильного шторма.</span>
</div>
<div>
<h2 class="weather-card__name">
Гондор
{{ item.geographic_name }}
</h2>
<div class="weather-card__time">
07:17
{{ item.current.dt }}
</div>
</div>
<div class="weather-conditions">
<div class="weather-conditions__icon" title="thunderstorm with heavy rain">⛈️</div>
<div class="weather-conditions__temp">15.0 °C</div>
<div class="weather-conditions__icon" :title="item.current.weather.description">{{weatherConditionIcons[item.current.weather.id]}}️</div>
<div class="weather-conditions__temp">{{ (item.current.temp - 273.15).toFixed(1) }} °C</div>
</div>
<div class="weather-details">
<div class="weather-details__item">
<div class="weather-details__item-label">Давление, мм рт. ст.</div>
<div class="weather-details__item-value">754</div>
<div class="weather-details__item-value">{{(item.current.pressure * 0.75).toFixed(0)}}</div>
</div>
<div class="weather-details__item">
<div class="weather-details__item-label">Влажность, %</div>
<div class="weather-details__item-value">90</div>
<div class="weather-details__item-value">{{item.current.humidity}}</div>
</div>
<div class="weather-details__item">
<div class="weather-details__item-label">Облачность, %</div>
<div class="weather-details__item-value">100</div>
<div class="weather-details__item-value">{{item.current.clouds}}</div>
</div>
<div class="weather-details__item">
<div class="weather-details__item-label">Ветер, м/с</div>
<div class="weather-details__item-value">10.5</div>
<div class="weather-details__item-value">{{item.current.wind_speed}}</div>
</div>
</div>
</li>
Expand Down
10 changes: 10 additions & 0 deletions 02-basics-2/10-counter/CounterApp.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.counter {
display: flex;
align-items: center;
gap: 20px;
}

.count {
font-size: 20px;
font-weight: bold;
}
26 changes: 26 additions & 0 deletions 02-basics-2/10-counter/CounterApp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { defineComponent } from 'vue'

export default defineComponent({
name: 'CounterApp',

setup() {},

template: `
<div class="counter">
<button
class="button button--secondary"
type="button"
aria-label="Decrement"
disabled
>➖</button>
<span class="count" data-testid="count">0</span>
<button
class="button button--secondary"
type="button"
aria-label="Increment"
>➕</button>
</div>
`,
})
29 changes: 29 additions & 0 deletions 02-basics-2/10-counter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Счётчик

👶🏻 _Несложная задача_\
📚 _Закрепление материала_

<!--start_statement-->

Требуется реализовать небольшое приложение — счётчик.
- Элемент выводит текущее значение счётчика и позволяет его менять от `0` до `5`
- Начальное значение — `0`
- Кнопка `+` увеличивает значение счётчика на `1`, а кнопка `-` — уменьшает
- Кнопки уменьшения и увеличения значения должны быть отключены с `disabled` при достижении минимального и максимального значений

### Результат

<img src="https://i.imgur.com/pV8p1HV.gif" alt="Пример" />

<!--end_statement-->

---

## Инструкция

📝 Для решения задачи отредактируйте файл: `CounterApp.js`.

🚀 Команда запуска для ручного тестирования: `npm run dev`\
Приложение будет доступно на [http://localhost:5173/02-basics-2/10-counter/](http://localhost:5173/02-basics-2/10-counter/).

✅ Доступно автоматическое тестирование: `npm test counter`
49 changes: 49 additions & 0 deletions 02-basics-2/10-counter/__tests__/counter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { mount } from '@vue/test-utils'
import type { VueWrapper, DOMWrapper } from '@vue/test-utils'
import CounterApp from '@/CounterApp.js'

describe('CounterApp', () => {
let wrapper: VueWrapper
let count: DOMWrapper<HTMLElement>
let decrement: DOMWrapper<HTMLButtonElement>
let increment: DOMWrapper<HTMLButtonElement>

beforeEach(() => {
wrapper = mount(CounterApp)
count = wrapper.find('[data-testid="count"]')
decrement = wrapper.find('[aria-label="Decrement"]')
increment = wrapper.find('[aria-label="Increment"]')
})

it('должно рендерить счетчик с начальным значением 0', () => {
expect(count.text()).toBe('0')
})

it('должно рендерить отключенной только кнопку уменьшения при значении счетчика 0', () => {
expect(decrement.attributes('disabled')).toBeDefined()
expect(increment.attributes('disabled')).not.toBeDefined()
})

it('должно увеличивать значение до 5 после 5 кликов на кнопку увеличения и отключить кнопку увеличения после этого', async () => {
for (let i = 0; i < 5; i++) {
await increment.trigger('click')
}
expect(count.text()).toBe('5')

Check failure on line 32 in 02-basics-2/10-counter/__tests__/counter.test.ts

View workflow job for this annotation

GitHub Actions / Run tests

02-basics-2/10-counter/__tests__/counter.test.ts > CounterApp > должно увеличивать значение до 5 после 5 кликов на кнопку увеличения и отключить кнопку увеличения после этого

AssertionError: expected '0' to be '5' // Object.is equality - Expected + Received - 5 + 0 ❯ 02-basics-2/10-counter/__tests__/counter.test.ts:32:26
expect(increment.attributes('disabled')).toBeDefined()
})

it('должно после увеличения до 5 уменьшать счетчик на 1 при нажатии на кнопку уменьшения и затем отключить кнопку уменьшения', async () => {
for (let i = 0; i < 5; i++) {
await increment.trigger('click')
}
expect(count.text()).toBe('5')

Check failure on line 40 in 02-basics-2/10-counter/__tests__/counter.test.ts

View workflow job for this annotation

GitHub Actions / Run tests

02-basics-2/10-counter/__tests__/counter.test.ts > CounterApp > должно после увеличения до 5 уменьшать счетчик на 1 при нажатии на кнопку уменьшения и затем отключить кнопку уменьшения

AssertionError: expected '0' to be '5' // Object.is equality - Expected + Received - 5 + 0 ❯ 02-basics-2/10-counter/__tests__/counter.test.ts:40:26

for (let i = 0; i < 5; i++) {
await decrement.trigger('click')
}
expect(count.text()).toBe('0')
expect(decrement.attributes('disabled')).toBeDefined()
expect(increment.attributes('disabled')).not.toBeDefined()
})
})
16 changes: 16 additions & 0 deletions 02-basics-2/10-counter/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>counter</title>
</head>
<body>
<div class="wrapper">
<div class="container">
<h1>Counter</h1>
<div id="app"></div>
</div>
</div>
<script type="module" src="./main.js"></script>
</body>
</html>
6 changes: 6 additions & 0 deletions 02-basics-2/10-counter/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import '@shgk/vue-course-ui/meetups/style.css'
import { createApp } from 'vue'
import CounterApp from './CounterApp.js'
import './CounterApp.css'

createApp(CounterApp).mount('#app')
11 changes: 11 additions & 0 deletions 02-basics-2/10-counter/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "@shgk/vue-course-taskbook/configs/tsconfig.json",
"include": ["**/*", "**/*.vue"],
"files": [],
"compilerOptions": {
"outDir": "dist",
"paths": {
"@/*": ["./*"]
}
}
}
17 changes: 17 additions & 0 deletions 02-basics-2/20-broken-map/MapApp.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.map {
display: flex;
width: fit-content;
background-color: #cfc;
position: relative;
}

.pin {
font-size: 24px;
line-height: 1em;
pointer-events: none;
user-select: none;
position: absolute;
transition: left ease 0.2s, top ease 0.2s;
/* Make position relative to pin end */
transform: translate(-50%, -100%);
}
39 changes: 39 additions & 0 deletions 02-basics-2/20-broken-map/MapApp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { defineComponent, ref, watch } from 'vue'

export default defineComponent({
name: 'MapApp',

setup() {
// Реактивные переменные для хранения координат метки
let x = ref(0)
let y = ref(0)

/**
* Обработчик клика по карте для установки координат метки
* @param {MouseEvent} event
*/
function handleClick(event) {
x = event.offsetX
y = event.offsetY
}

// Следим за X и Y для установки нового положения
watch([x, y], () => {
// Находим метку и изменяем её положение
const map = document.querySelector('.pin')
map.style.left = `${x}px`
map.style.top = `${y}px`
})

return {
handleClick,
}
},

template: `
<div class="map" @click="handleClick">
<img class="map-image" src="./map.png" alt="Map" draggable="false" />
<span class="pin">📍</span>
</div>
`,
})
29 changes: 29 additions & 0 deletions 02-basics-2/20-broken-map/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Сломанная карта

👷🏻 _Задача нормальной сложности_\
🐞 _Исправление ошибок_

<!--start_statement-->

Реализовано небольшое Vue приложение - карта. Клик по карте переносит метку на место клика.

К сожалению, приложение не работает. Найдите и исправьте 2 ошибки:
1. Фактическую ошибку - приводящую карту в нерабочее состояние
2. Концептуальную ошибку - использование некорректных подходов в Vue

### Результат

<img src="https://i.imgur.com/coSeggb.gif" alt="Пример" />

<!--end_statement-->

---

## Инструкция

📝 Для решения задачи отредактируйте файл: `MapApp.js`.

🚀 Команда запуска для ручного тестирования: `npm run dev`\
Приложение будет доступно на [http://localhost:5173/02-basics-2/20-broken-map/](http://localhost:5173/02-basics-2/20-broken-map/).

✅ Доступно автоматическое тестирование: `npm test broken-map`
25 changes: 25 additions & 0 deletions 02-basics-2/20-broken-map/__tests__/broken-map.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import MapApp from '@/MapApp.js'
import { nextTick } from 'vue'

describe('MapApp', () => {
it('должен устанавливать style свойства left и top у метки .pin в соответствии с offsetX и offsetY клика по карте', async () => {
const wrapper = mount(MapApp, { attachTo: document.body })
await wrapper.trigger('click', { offsetX: 42, offsetY: 24 })
const pin = wrapper.find<HTMLElement>('.pin')
expect(pin.element.style.left).toMatch('42px')

Check failure on line 11 in 02-basics-2/20-broken-map/__tests__/broken-map.test.ts

View workflow job for this annotation

GitHub Actions / Run tests

02-basics-2/20-broken-map/__tests__/broken-map.test.ts > MapApp > должен устанавливать style свойства left и top у метки .pin в соответствии с offsetX и offsetY клика по карте

AssertionError: expected '' to match '42px' - Expected + Received - 42px ❯ 02-basics-2/20-broken-map/__tests__/broken-map.test.ts:11:36
expect(pin.element.style.top).toMatch('24px')
})

it('должен использовать Vue подходы', async () => {
const wrapper = mount(MapApp, { attachTo: document.body })
await wrapper.trigger('click', { offsetX: 42, offsetY: 24 })
// Упс, компонент ре-рендерится
wrapper.vm.$forceUpdate()
await nextTick()
const pin = wrapper.find<HTMLElement>('.pin')
expect(pin.element.style.left).toMatch('42px')

Check failure on line 22 in 02-basics-2/20-broken-map/__tests__/broken-map.test.ts

View workflow job for this annotation

GitHub Actions / Run tests

02-basics-2/20-broken-map/__tests__/broken-map.test.ts > MapApp > должен использовать Vue подходы

AssertionError: expected '' to match '42px' - Expected + Received - 42px ❯ 02-basics-2/20-broken-map/__tests__/broken-map.test.ts:22:36
expect(pin.element.style.top).toMatch('24px')
})
})
11 changes: 11 additions & 0 deletions 02-basics-2/20-broken-map/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Сделаем поиск ошибки чуть сложнее
// Если вы случайно открыли этот файл - закройте обратно ༼ つ ◕_◕ ༽つ

export default [
{
files: ['./MapApp.js'],
rules: {
'vue/no-ref-as-operand': 'off',
},
},
]
16 changes: 16 additions & 0 deletions 02-basics-2/20-broken-map/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Сломанная карта</title>
</head>
<body>
<div class="wrapper">
<div class="container">
<h1>Map</h1>
<div id="app"></div>
</div>
</div>
<script type="module" src="./main.js"></script>
</body>
</html>
6 changes: 6 additions & 0 deletions 02-basics-2/20-broken-map/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import '@shgk/vue-course-ui/meetups/style.css'
import './MapApp.css'
import { createApp } from 'vue'
import MapApp from './MapApp.js'

createApp(MapApp).mount('#app')
Binary file added 02-basics-2/20-broken-map/map.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions 02-basics-2/20-broken-map/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "@shgk/vue-course-taskbook/configs/tsconfig.json",
"include": ["**/*", "**/*.vue"],
"files": [],
"compilerOptions": {
"outDir": "dist",
"paths": {
"@/*": ["./*"]
}
}
}
Loading

0 comments on commit 4cb2177

Please sign in to comment.