-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
- Loading branch information
There are no files selected for viewing
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; | ||
} |
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> | ||
`, | ||
}) |
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` |
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 GitHub Actions / Run tests02-basics-2/10-counter/__tests__/counter.test.ts > CounterApp > должно увеличивать значение до 5 после 5 кликов на кнопку увеличения и отключить кнопку увеличения после этого
|
||
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 GitHub Actions / Run tests02-basics-2/10-counter/__tests__/counter.test.ts > CounterApp > должно после увеличения до 5 уменьшать счетчик на 1 при нажатии на кнопку уменьшения и затем отключить кнопку уменьшения
|
||
|
||
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() | ||
}) | ||
}) |
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> |
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') |
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": { | ||
"@/*": ["./*"] | ||
} | ||
} | ||
} |
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%); | ||
} |
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> | ||
`, | ||
}) |
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` |
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 GitHub Actions / Run tests02-basics-2/20-broken-map/__tests__/broken-map.test.ts > MapApp > должен устанавливать style свойства left и top у метки .pin в соответствии с offsetX и offsetY клика по карте
|
||
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 GitHub Actions / Run tests02-basics-2/20-broken-map/__tests__/broken-map.test.ts > MapApp > должен использовать Vue подходы
|
||
expect(pin.element.style.top).toMatch('24px') | ||
}) | ||
}) |
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', | ||
}, | ||
}, | ||
] |
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> |
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') |
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": { | ||
"@/*": ["./*"] | ||
} | ||
} | ||
} |