В Svelte концепция реактивности является одной из основ, на которой строится весь фреймворк. Важным элементом, влияющим на реактивность, являются stores — абстракции, которые позволяют управлять состоянием в приложении и обеспечивают обмен данными между компонентами. Тестирование реактивности и работы с store’ами требует особого подхода, поскольку важна не только правильная логика приложения, но и корректное обновление интерфейса при изменении данных. В данной главе рассмотрим, как тестировать работу с реактивностью и stores в Svelte.
В Svelte stores — это объект, который содержит данные и предоставляет методы для их изменения и наблюдения за этими изменениями. В стандартном Svelte можно использовать три типа stores:
Для создания writable store используется функция
writable:
import { writable } from 'svelte/store';
const count = writable(0);
Для работы с данными можно подписываться на store, используя метод
$ в компоненте:
<script>
import { count } from './store.js';
</script>
<p>Счёт: {$count}</p>
Тестирование реактивности и правильного обновления данных начинается с проверки того, как store изменяется и как эти изменения отражаются в компоненте. Важно, чтобы любые изменения в состоянии сразу вызывали обновление интерфейса.
Для тестирования компонентов с использованием Svelte можно воспользоваться Svelte Testing Library. Она предоставляет удобный API для проверки интерфейса, в том числе для тестирования реактивных изменений.
Основная задача при тестировании — убедиться, что интерфейс корректно обновляется после изменения состояния. В следующем примере показано, как можно протестировать компонент с использованием writable store.
// Счётчик.svelte
<script>
import { writable } from 'svelte/store';
export let count = writable(0);
</script>
<button on:click={() => $count += 1}>Увеличить</button>
<p>Счёт: {$count}</p>
Тестирование этого компонента с использованием Svelte Testing Library:
import { render, fireEvent } from '@testing-library/svelte';
import Counter from './Counter.svelte';
import { writable } from 'svelte/store';
test('увеличивает счёт при нажатии на кнопку', async () => {
const { getByText } = render(Counter, { count: writable(0) });
const button = getByText('Увеличить');
const paragraph = getByText('Счёт: 0');
await fireEvent.click(button);
expect(paragraph).toHaveTextContent('Счёт: 1');
await fireEvent.click(button);
expect(paragraph).toHaveTextContent('Счёт: 2');
});
В данном примере создаётся тест, который проверяет, что при каждом нажатии на кнопку счёт увеличивается на единицу, и интерфейс обновляется соответствующим образом.
Тестирование derived store также важно, поскольку оно обеспечивает реактивность на основе других stores. Например, если необходимо вычислить значение, которое зависит от других данных, используется derived store.
Пример компонента:
// App.svelte
<script>
import { writable, derived } from 'svelte/store';
const a = writable(2);
const b = writable(3);
const sum = derived([a, b], ([$a, $b]) => $a + $b);
</script>
<p>Сумма: {$sum}</p>
<button on:click={() => $a += 1}>Увеличить A</button>
<button on:click={() => $b += 1}>Увеличить B</button>
Тестирование этого компонента:
import { render, fireEvent } from '@testing-library/svelte';
import App from './App.svelte';
import { writable } from 'svelte/store';
test('обновляет сумму при изменении A и B', async () => {
const { getByText } = render(App);
const sumText = getByText('Сумма: 5');
const buttonA = getByText('Увеличить A');
const buttonB = getByText('Увеличить B');
await fireEvent.click(buttonA);
expect(sumText).toHaveTextContent('Сумма: 6');
await fireEvent.click(buttonB);
expect(sumText).toHaveTextContent('Сумма: 7');
});
В данном случае тест проверяет, что сумма обновляется корректно при
изменении значений a и b.
Часто в приложениях возникает необходимость работать с асинхронными
операциями, например, загрузка данных с сервера. Для тестирования таких
операций можно использовать async и await в
тестах. В Svelte store можно также асинхронно обновлять данные.
Пример компонента с асинхронным store:
<script>
import { writable } from 'svelte/store';
const data = writable([]);
async function fetchData() {
const response = await fetch('/api/data');
const result = await response.json();
data.set(result);
}
fetchData();
</script>
<ul>
{#each $data as item}
<li>{item.name}</li>
{/each}
</ul>
Тестирование этого компонента:
import { render, screen, waitFor } from '@testing-library/svelte';
import App from './App.svelte';
test('загружает данные и отображает их', async () => {
const mockData = [{ name: 'Item 1' }, { name: 'Item 2' }];
global.fetch = jest.fn().mockResolvedValue({
json: jest.fn().mockResolvedValue(mockData)
});
render(App);
await waitFor(() => expect(screen.getByText('Item 1')).toBeInTheDocument());
expect(screen.getByText('Item 2')).toBeInTheDocument();
});
Здесь используется мокирование fetch, чтобы имитировать
загрузку данных и тестировать асинхронное обновление store.
Для более сложных случаев можно мокировать сам store или его значения, чтобы контролировать тестируемое поведение.
Пример мокирования writable store:
import { writable } from 'svelte/store';
jest.mock('svelte/store', () => ({
writable: jest.fn(() => ({
subscribe: jest.fn(callback => callback(5)),
set: jest.fn(),
update: jest.fn()
}))
}));
import Counter from './Counter.svelte';
import { render } from '@testing-library/svelte';
test('проверка работы с мокированным store', () => {
const { getByText } = render(Counter);
expect(getByText('Счёт: 5')).toBeInTheDocument();
});
Здесь используется мок для store, чтобы гарантировать возвращение фиксированного значения и протестировать его взаимодействие с компонентом.
Тестирование реактивных данных и store в Svelte требует внимательности к деталям, поскольку оно связано не только с правильной логикой, но и с тем, как данные влияют на обновление интерфейса. Важно использовать соответствующие инструменты, такие как Svelte Testing Library, для проверки корректности работы с состоянием, и быть готовым к тестированию как синхронных, так и асинхронных операций. Тщательное тестирование помогает обеспечить стабильность приложения и правильность его работы в различных сценариях.