Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 @@ -38,6 +39,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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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));
}