Skip to content

Commit

Permalink
Добавил новые функции
Browse files Browse the repository at this point in the history
  • Loading branch information
LeMarck committed Mar 3, 2024
1 parent cbb6650 commit 393110c
Show file tree
Hide file tree
Showing 9 changed files with 5,766 additions and 6,730 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"extends": [
"eslint:recommended",
"@tinkoff/eslint-config/app",
"@tinkoff/eslint-config/lib",
"@tinkoff/eslint-config/jest",
"plugin:import/typescript"
],
Expand Down
12,277 changes: 5,612 additions & 6,665 deletions package-lock.json

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "store-track",
"version": "0.1.0",
"version": "0.2.0",
"description": "Naive implementation of the state manager library Effector",
"author": {
"name": "Evgeny Petrov",
Expand All @@ -11,7 +11,7 @@
"private": true,
"main": "dist/index.js",
"scripts": {
"test": "eslint . && jest",
"test": "eslint . && jest --coverage",
"build": "tsc"
},
"keywords": [
Expand All @@ -37,12 +37,12 @@
"README.md"
],
"devDependencies": {
"@tinkoff/eslint-config": "1.22.0",
"@types/jest": "27.0.3",
"eslint": "7.32.0",
"jest": "27.4.5",
"ts-jest": "27.1.1",
"typescript": "4.5.4"
"@tinkoff/eslint-config": "2.0.0",
"@types/jest": "29.5.12",
"eslint": "8.57.0",
"jest": "29.7.0",
"ts-jest": "29.1.2",
"typescript": "5.3.3"
},
"engines": {
"node": ">=12"
Expand Down
42 changes: 26 additions & 16 deletions src/effect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('createEffect', () => {
await expect(emptyEffect(1)).rejects.toThrow('No handler used in effect');
});

it('`.use`: определяет имплементацию эффекта: функцию, которая будет вызвана при срабатывании\n', () => {
it('`.use`: определяет имплементацию эффекта: функцию, которая будет вызвана при срабатывании', () => {
const effectFn = jest.fn();
const effect = createEffect(jest.fn);

Expand All @@ -33,8 +33,8 @@ describe('createEffect', () => {
const result = await updateUserFx({ name: 'John', role: 'admin' });

expect(result).toBe(true);
expect(mockUserNameUpdatedWatch).toBeCalledWith('John');
expect(mockUserRoleUpdatedWatch).toBeCalledWith('ADMIN');
expect(mockUserNameUpdatedWatch).toHaveBeenCalledWith('John');
expect(mockUserRoleUpdatedWatch).toHaveBeenCalledWith('ADMIN');
});

it('`.prepend`: создаёт событие-триггер для преобразования данных перед запуском эффекта', () => {
Expand All @@ -45,7 +45,7 @@ describe('createEffect', () => {
updateUserFx.watch(mockWatcher);

changeName('John');
expect(mockWatcher).toBeCalledWith({ role: 'user', name: 'John' });
expect(mockWatcher).toHaveBeenCalledWith({ role: 'user', name: 'John' });
});

it('`.watch`: вызывает дополнительную функцию с сайд-эффектами при каждом срабатывании эффекта', async () => {
Expand All @@ -54,19 +54,21 @@ describe('createEffect', () => {
const unwatch = fx.watch(watcherMock);

await expect(fx({ title: 'Title1' })).resolves.toBe('Title1');
expect(watcherMock).toBeCalledWith({ title: 'Title1' });
expect(watcherMock).toBeCalledTimes(1);
expect(watcherMock).toHaveBeenCalledWith({ title: 'Title1' });
expect(watcherMock).toHaveBeenCalledTimes(1);

unwatch();

await expect(fx({ title: 'Title2' })).resolves.toBe('Title2');
expect(watcherMock).toBeCalledTimes(1);
expect(watcherMock).toHaveBeenCalledTimes(1);
});

it('`.finally`: событие, которое срабатывает при завершении эффекта с подробной информацией об аргументах, результатах и статусе выполнения', async () => {
const watcherMock = jest.fn();
const fetchApiFx = createEffect(async ({ ok }: { ok: boolean }) => {
await new Promise((resolve) => setTimeout(resolve, 500));
await new Promise((resolve) => {
setTimeout(resolve, 500);
});
if (ok) return 'SUCCESS';
throw Error('ERROR');
});
Expand All @@ -75,7 +77,7 @@ describe('createEffect', () => {

const result = await fetchApiFx({ ok: true });
expect(result).toEqual('SUCCESS');
expect(watcherMock).toBeCalledWith({
expect(watcherMock).toHaveBeenCalledWith({
status: 'done',
params: {
ok: true,
Expand All @@ -84,7 +86,7 @@ describe('createEffect', () => {
});

await expect(fetchApiFx({ ok: false })).rejects.toThrow('ERROR');
expect(watcherMock).toBeCalledWith({
expect(watcherMock).toHaveBeenCalledWith({
status: 'fail',
params: {
ok: false,
Expand All @@ -94,14 +96,22 @@ describe('createEffect', () => {
});

it('`.pending`: стор, который показывает, что эффект находится в процессе выполнения', async () => {
const fetchApiFx = createEffect(async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));
const watcherMock = jest.fn();
const fetchApiFx = createEffect(
async (ms: number) =>
new Promise((resolve) => {
setTimeout(resolve, ms);
})
);

fetchApiFx.pending.watch(watcherMock);

await fetchApiFx(100);

expect(watcherMock.mock.calls).toEqual([[true], [false]]);
expect(watcherMock.mock.calls).toEqual([
[true, true],
[false, false],
]);
});

it('`.done/doneData`: события, которое срабатывают с результатом выполнения эффекта', async () => {
Expand All @@ -114,8 +124,8 @@ describe('createEffect', () => {

await fetchApiFx(42);

expect(doneDataWatcherMock).toBeCalledWith(42);
expect(doneWatcherMock).toBeCalledWith({ params: 42, result: 42 });
expect(doneDataWatcherMock).toHaveBeenCalledWith(42);
expect(doneWatcherMock).toHaveBeenCalledWith({ params: 42, result: 42 });
});

it('`.fail/failData`: события, которое срабатывают с ошибкой, возникшей при выполнении эффекта', async () => {
Expand All @@ -130,7 +140,7 @@ describe('createEffect', () => {

await fetchApiFx('42').catch(() => 1);

expect(failDataWatcherMock).toBeCalledWith(Error('42'));
expect(failWatcherMock).toBeCalledWith({ params: '42', error: Error('42') });
expect(failDataWatcherMock).toHaveBeenCalledWith(Error('42'));
expect(failWatcherMock).toHaveBeenCalledWith({ params: '42', error: Error('42') });
});
});
4 changes: 2 additions & 2 deletions src/effect.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Event } from './event';
import type { Store } from './store';
import { createEvent } from './event';
import type { Event } from './event';
import { createStore } from './store';
import type { Store } from './store';
import type { Unsubscribe } from './types.h';

type EffectFinally<Params, Done, Fail = Error> =
Expand Down
43 changes: 33 additions & 10 deletions src/event.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ describe('createEvent', () => {
const unwatch = event.watch(watcherMock);

event('Hello!');
expect(watcherMock).toBeCalledWith('Hello!');
expect(watcherMock).toHaveBeenCalledWith('Hello!');

unwatch();
event('World');
expect(watcherMock).not.toBeCalledWith('World');
expect(watcherMock).toBeCalledTimes(1);
expect(watcherMock).not.toHaveBeenCalledWith('World');
expect(watcherMock).toHaveBeenCalledTimes(1);
});

it('`.map`: создает производное событие на основе данных из исходного', () => {
Expand All @@ -33,8 +33,8 @@ describe('createEvent', () => {

updateUser({ name: 'John', role: 'admin' });

expect(userNameUpdatedWatcherMock).toBeCalledWith('John');
expect(userRoleUpdatedWatcherMock).toBeCalledWith('ADMIN');
expect(userNameUpdatedWatcherMock).toHaveBeenCalledWith('John');
expect(userRoleUpdatedWatcherMock).toHaveBeenCalledWith('ADMIN');
});

it('`.prepend`: создает событие-триггер для преобразования данных перед запуском исходного эвента', () => {
Expand All @@ -52,13 +52,13 @@ describe('createEvent', () => {
userPropertyChanged.watch(watcherMock);

changeName('John');
expect(watcherMock).toBeCalledWith({ field: 'name', value: 'John' });
expect(watcherMock).toHaveBeenCalledWith({ field: 'name', value: 'John' });

changeRole('admin');
expect(watcherMock).toBeCalledWith({ field: 'role', value: 'ADMIN' });
expect(watcherMock).toHaveBeenCalledWith({ field: 'role', value: 'ADMIN' });

changeName('Alice');
expect(watcherMock).toBeCalledWith({ field: 'name', value: 'Alice' });
expect(watcherMock).toHaveBeenCalledWith({ field: 'name', value: 'Alice' });
});

it('`.filterMap`: создает производное событие на основе данных из исходного с возможностью отмены вызова', () => {
Expand All @@ -69,9 +69,32 @@ describe('createEvent', () => {
effectorFound.watch(watcherMock);

listReceived(['redux', 'mobx']);
expect(watcherMock).not.toBeCalled();
expect(watcherMock).not.toHaveBeenCalled();

listReceived(['redux', 'effector', 'mobx']);
expect(watcherMock).toBeCalledWith('effector');
expect(watcherMock).toHaveBeenCalledWith('effector');
});

it('`.filter`: cоздает производное событие с возможностью отмены вызова', () => {
const numbers = createEvent<{ x: number }>();
const positiveNumbers = numbers.filter({ fn: ({ x }) => x > 0 });
const numberWatcherMock = jest.fn();
const watcherMock = jest.fn();

numbers.watch(numberWatcherMock);
positiveNumbers.watch(watcherMock);

numbers({ x: 0 });
expect(watcherMock).not.toHaveBeenCalled();
expect(numberWatcherMock).toHaveBeenCalled();

numbers({ x: -10 });
expect(watcherMock).not.toHaveBeenCalled();
expect(numberWatcherMock).toHaveBeenCalled();

numbers({ x: 10 });
expect(watcherMock).toHaveBeenCalled();
expect(watcherMock).toHaveBeenCalledWith({ x: 10 });
expect(numberWatcherMock).toHaveBeenCalled();
});
});
15 changes: 14 additions & 1 deletion src/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ export interface Event<Payload = void> {
map<NewPayload>(fn: (payload: Payload) => NewPayload): Event<NewPayload>;
prepend<NewPayload>(fn: (payload: NewPayload) => Payload): Event<NewPayload>;
filterMap<NewPayload>(fn: (payload: Payload) => NewPayload | void): Event<NewPayload>;
filter(config: { fn: (payload: Payload) => boolean }): Event<Payload>;
}

export function createEvent<Payload = void>(): Event<Payload> {
const watchers: Set<(payload: Payload) => void> = new Set<(payload: Payload) => void>();
const watchers: Set<(payload: Payload) => void> = new Set();

function action(payload: Payload): void {
watchers.forEach((subscriber) => subscriber(payload));
Expand Down Expand Up @@ -52,5 +53,17 @@ export function createEvent<Payload = void>(): Event<Payload> {
return event;
};

action.filter = (config: { fn: (payload: Payload) => boolean }): Event<Payload> => {
const event = createEvent<Payload>();

action.watch((payload) => {
if (config.fn(payload)) {
event(payload);
}
});

return event;
};

return action;
}
56 changes: 46 additions & 10 deletions src/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ describe('createStore', () => {
it('`.on`: обновляет состояние стора с помощью функции-обработчика при срабатывании триггера', () => {
const eventMock = jest.fn(<T>() => createEvent<T>())();
const reducerMock = jest.fn(() => 1);
const $store = createStore(0).on(eventMock, reducerMock);
const $store = createStore(0).on([eventMock], reducerMock);

expect($store.getState()).toBe(0);

eventMock(10);

expect(reducerMock).toBeCalledWith(0, 10);
expect(reducerMock).toHaveBeenCalledWith(0, 10);
expect($store.getState()).toBe(1);
});

Expand All @@ -32,18 +32,34 @@ describe('createStore', () => {
$store.watch(foo, watcherMock);

add(4);
expect(watcherMock).toBeCalledWith(4);
expect(watcherMock).toHaveBeenCalledWith(4, 4);
expect($store.getState()).toBe(4);

add(3);
expect(watcherMock).toBeCalledWith(7);
expect(watcherMock).toHaveBeenCalledWith(7, 3);
expect($store.getState()).toBe(7);

foo(42);
expect(watcherMock).toBeCalledWith(7, 42);
expect(watcherMock).toHaveBeenCalledWith(7, 42);
expect($store.getState()).toBe(7);
});

it('`unwatch` прекращает отслеживание', () => {
const watcherMock = jest.fn();
const add = createEvent<number>();
const $store = createStore(0).on(add, (state, payload) => state + payload);

const unwatch = $store.watch(watcherMock);

add(42);
expect(watcherMock).toHaveBeenCalledWith(42, 42);

unwatch();
add(123);
add(456);
expect(watcherMock).toHaveBeenCalledTimes(1);
});

it('`.map`: создает производный стор на основе данных из исходного', () => {
const watcherMock = jest.fn();
const changed = createEvent<string>();
Expand All @@ -52,15 +68,15 @@ describe('createStore', () => {
$title.map((title) => title.length).watch(watcherMock);

changed('hello');
expect(watcherMock).toBeCalledWith(5);
expect(watcherMock).toBeCalledTimes(1);
expect(watcherMock).toHaveBeenCalledWith(5, 5);
expect(watcherMock).toHaveBeenCalledTimes(1);

changed('world');
expect(watcherMock).toBeCalledTimes(1);
expect(watcherMock).toHaveBeenCalledTimes(1);

changed('hello world');
expect(watcherMock).toBeCalledWith(11);
expect(watcherMock).toBeCalledTimes(2);
expect(watcherMock).toHaveBeenCalledWith(11, 11);
expect(watcherMock).toHaveBeenCalledTimes(2);
});

it('`.off`: удаляет обработчик для данного триггера', () => {
Expand All @@ -75,4 +91,24 @@ describe('createStore', () => {
changed(100500);
expect($store.getState()).toBe(42);
});

it('`.reset`: cбрасывает состояние стора к исходному значению при срабатывании триггера', () => {
const watcherMock = jest.fn();
const increment = createEvent();
const reset = createEvent();
const $store = createStore(0)
.on(increment, (state) => state + 1)
.reset(reset);

$store.watch(watcherMock);

increment();
expect($store.getState()).toBe(1);

increment();
expect($store.getState()).toBe(2);

reset();
expect($store.getState()).toBe(0);
});
});
Loading

0 comments on commit 393110c

Please sign in to comment.