Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/babel-plugin-factories.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"patronum/either",
"patronum/empty",
"patronum/equals",
"patronum/includes",
"patronum/every",
"patronum/format",
"patronum/in-flight",
Expand Down Expand Up @@ -37,6 +38,7 @@
"either": "either",
"empty": "empty",
"equals": "equals",
"includes": "includes",
"every": "every",
"format": "format",
"inFlight": "in-flight",
Expand Down
86 changes: 86 additions & 0 deletions src/includes/includes.fork.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { allSettled, createEvent, createStore, fork } from 'effector';
import { includes } from './index';

it('should return true if number is included in array', async () => {
const $array = createStore([1, 2, 3]);
const $result = includes($array, 2);

const scope = fork();
expect(scope.getState($result)).toBe(true);
});

it('should return false if number is not included in array', async () => {
const $array = createStore([1, 2, 3]);
const $result = includes($array, 4);

const scope = fork();
expect(scope.getState($result)).toBe(false);
});

it('should return true if store number is included in array', async () => {
const $array = createStore([1, 2, 3]);
const $findInArray = createStore(2);
const $result = includes($array, $findInArray);

const scope = fork();
expect(scope.getState($result)).toBe(true);
});

it('should return true if string is included in string store', async () => {
const $string = createStore('Hello world!');
const $result = includes($string, 'Hello');

const scope = fork();
expect(scope.getState($result)).toBe(true);
});

it('should return false if string is not included in string store', async () => {
const $string = createStore('Hello world!');
const $result = includes($string, 'Goodbye');

const scope = fork();
expect(scope.getState($result)).toBe(false);
});

it('should return true if store string is included in string store', async () => {
const $string = createStore('Hello world!');
const $findInString = createStore('Hello');
const $result = includes($string, $findInString);

const scope = fork();
expect(scope.getState($result)).toBe(true);
});

it('should update result if array store changes', async () => {
const updateArray = createEvent<number[]>();
const $array = createStore([1, 2, 3]).on(updateArray, (_, newArray) => newArray);
const $result = includes($array, 4);

const scope = fork();
expect(scope.getState($result)).toBe(false);

await allSettled(updateArray, { scope, params: [1, 2, 3, 4] });
expect(scope.getState($result)).toBe(true);
});

it('should update result if string store changes', async () => {
const changeString = createEvent<string>();
const $array = createStore('Hello world!');
const $findInArray = createStore('Goodbye').on(changeString, (_, newString) => newString);
const $result = includes($array, $findInArray);

const scope = fork();
expect(scope.getState($result)).toBe(false);

await allSettled(changeString, { scope, params: 'world' });
expect(scope.getState($result)).toBe(true);
});

it('should throw an error if first argument is a number instead of array or string', () => {
const $number = createStore(5);
const $findNumber = createStore(5);

expect(() => includes(($number as any), $findNumber)).toThrowError(
'first argument should be an unit of array or string'
);
});
82 changes: 82 additions & 0 deletions src/includes/includes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { createEvent, createStore } from 'effector';
import { includes } from './index';
import { not } from '../not';

describe('includes', () => {
it('should return true if number is included in array', () => {
const $array = createStore([1, 2, 3]);
const $result = includes($array, 2);

expect($result.getState()).toBe(true);
});

it('should return false if number is not included in array', () => {
const $array = createStore([1, 2, 3]);
const $result = includes($array, 4);

expect($result.getState()).toBe(false);
});

it('should return true if store number is included in array', () => {
const $array = createStore([1, 2, 3]);
const $findInArray = createStore(2);
const $result = includes($array, $findInArray);

expect($result.getState()).toBe(true);
});

it('should return true if store number is not included in array', () => {
const $array = createStore([1, 2, 3]);
const $findInArray = createStore(4);
const $result = includes($array, $findInArray);

expect($result.getState()).toBe(false);
});

it('should return true if string is included in string store', () => {
const $string = createStore('Hello world!');
const $result = includes($string, 'Hello');

expect($result.getState()).toBe(true);
});

it('should return false if string is not included in string store', () => {
const $string = createStore('Hello world!');
const $result = includes($string, 'Goodbye');

expect($result.getState()).toBe(false);
});

it('should return true if store string is included in string store', () => {
const $string = createStore('Hello world!');
const $findInString = createStore('Hello');
const $result = includes($string, $findInString);

expect($result.getState()).toBe(true);
});

it('should return true if store string is not included in string store', () => {
const $string = createStore('Hello world!');
const $findInString = createStore('Goodbye');
const $result = includes($string, $findInString);

expect($result.getState()).toBe(false);
});

it('should composed with other methods in patronum', () => {
const $string = createStore('Hello world!');
const $findInString = createStore('Goodbye');
const $result = not(includes($string, $findInString));

expect($result.getState()).toBe(true);
});

it('should throw an error if first argument is a number instead of array or string', () => {
const $number = createStore(5);
const $findNumber = createStore(5);

expect(() => includes(($number as any), $findNumber)).toThrowError(
'first argument should be an unit of array or string'
);
});
});
26 changes: 26 additions & 0 deletions src/includes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { combine, Store } from 'effector';

export function includes <T extends string>(
a: Store<T>,
b: Store<T> | T,
): Store<boolean>
export function includes <T extends string | number>(
a: Store<Array<T>>,
b: Store<T> | T,
): Store<boolean>
Comment on lines +7 to +10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why only string | number?

Comment on lines +3 to +10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if:

Suggested change
export function includes <T extends string>(
a: Store<T>,
b: Store<T> | T,
): Store<boolean>
export function includes <T extends string | number>(
a: Store<Array<T>>,
b: Store<T> | T,
): Store<boolean>
export function includes <T extends string>(
a: Store<T>,
b: Store<T> | T,
): Store<boolean>
export function includes <T>(
a: Store<Array<T>>,
b: Store<T> | T,
): Store<boolean>
export function includes <T>(
a: Store<{ ref: Set<T> }>,
b: Store<T> | T,
): Store<boolean>

allow to compare with any list of items?

export function includes <T extends string | number>(
a: Store<T | Array<T>>,
b: Store<T> | T,
Comment on lines +12 to +13
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
a: Store<T | Array<T>>,
b: Store<T> | T,
haystack: Store<T | Array<T>>,
needle: Store<T> | T,

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a, b is not clearly explains what in what we checking:

includes($email, "@");
includes("@", $email);

it will work by types in both ways, but IDE will suggest a:, b: names which is not self-descriptive, I think

): Store<boolean> {
return combine(a, b, (a, b) => {
if (Array.isArray(a)) {
return a.includes(b);
}

if (typeof a === 'string') {
return a.includes(b as string);
}

throw new Error('first argument should be an unit of array or string');
});
}
88 changes: 88 additions & 0 deletions src/includes/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
title: includes
slug: includes
description: Checks if a value exists within a store containing a string or an array.
group: predicate
---

```ts
import { includes } from 'patronum';
// or
import { includes } from 'patronum/includes';
```

### Motivation

The includes method allows checking if a store (containing either a string or an array) includes a specified value. This value can either be written as a literal or provided through another store.

### Formulae

```ts
$isInclude = includes(container, value);
```

- `$isInclude` will be a store containing `true` if the `value` exists in `container`

### Arguments

1. `container: Store<string> | Store<Array<T>>` — A store with a string or an array where the `value` will be searched.
2. `value: T | Store<T>` — A literal value or a store containing the value to be checked for existence in `container`.

### Returns

- `$isInclude: Store<boolean>` — The store containing `true` if value exists within `container`, false otherwise.

### Example

#### Checking in Array

```ts
const $array = createStore([1, 2, 3]);
const $isInclude = includes($array, 2);

console.assert($isInclude.getState() === true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think is better do not recommend using .getState() in documentation.

Suggested change
console.assert($isInclude.getState() === true);
$isInclude // => true

```

#### Checking in String

```ts
const $string = createStore('Hello world!');
const $isInclude = includes($string, 'Hello');

console.assert($isInclude.getState() === true);
```

### Composition

The `includes` operator can be composed with other methods in patronum:

```ts
import { not } from 'patronum';

const $greeting = createStore('Hello world!');
const $isNotInclude = not(includes($greeting, 'Goodbye'));
// $isNotInclude contains `true` only when 'Goodbye' is not in $greeting
```

### Alternative

Compare to literal value:

```ts
import { createStore } from 'effector';
const $array = createStore([1, 2, 3]);
const $isInclude = $array.map((array) => array.includes(2));

console.assert($isInclude.getState() === true);
```

Compare to another store:

```ts
import { createStore, combine } from 'effector';
const $array = createStore([1, 2, 3]);
const $value = createStore(2);
const $isInclude = combine($array, $value, (array, value) => array.includes(value));

console.assert($isInclude.getState() === true);
```
58 changes: 58 additions & 0 deletions test-typings/includes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { expectType } from 'tsd';
import { createStore, Store } from 'effector';
import { includes } from '../dist/includes';

/**
* Should accept a string or array store with a compatible value
*/
{
expectType<Store<boolean>>(includes(createStore('Hello world!'), 'Hello'));
expectType<Store<boolean>>(includes(createStore(['a', 'b', 'c']), 'b'));
expectType<Store<boolean>>(includes(createStore([1, 2, 3]), 2));
}

/**
* Should accept a compatible value store
*/
{
const $stringStore = createStore('Hello world!');
const $searchString = createStore('Hello');
expectType<Store<boolean>>(includes($stringStore, $searchString));

const $arrayStore = createStore([1, 2, 3]);
const $searchNumber = createStore(2);
expectType<Store<boolean>>(includes($arrayStore, $searchNumber));
}

/**
* Should reject stores with incompatible types
*/
{
// @ts-expect-error
includes(createStore('Hello world!'), 1);
// @ts-expect-error
includes(createStore([1, 2, 3]), 'Hello');
// @ts-expect-error
includes(createStore(['a', 'b', 'c']), 1);
// @ts-expect-error
includes(createStore([1, 2, 3]), createStore('Hello'));
}

// Should reject stores and literals with incompatible types
{
// @ts-expect-error
includes(createStore(true), 'Hello');
// @ts-expect-error
includes('Hello', createStore(1));
// @ts-expect-error
includes(createStore([1, 2, 3]), true);
// @ts-expect-error
includes(createStore(['a', 'b', 'c']), 1);
}

// Should allow literal values compatible with the store type
{
expectType<Store<boolean>>(includes(createStore('Hello world!'), 'Hello'));
expectType<Store<boolean>>(includes(createStore(['a', 'b', 'c']), 'b'));
expectType<Store<boolean>>(includes(createStore([1, 2, 3]), 2));
}