Repository: effector/effector (ts)

Issue:
`allSettled` doesn't support `$store` as argument silently 
**What is the current behavior:**

If we provide any store to `allSettled` it will change it's value, but later `serialize` won't detect it.

**Please provide the steps to reproduce and if possible a minimal demo of the problem via https://share.effector.dev, https://codesandbox.io or similar**

https://share.effector.dev/wcQz3AOQ

**What is the expected behavior:**

Throw exception on passing store to `allSettled` or fully support it.

**Which versions of effector packages, and which browser and OS are affected by this issue? Did this work in previous versions of effector?:**

`master`


Pull request description:
Support store as `allSettled` argument
Fixes #608 (by fixing serialize issue)

Changelog:
- added tests for `allSettled($store, ...)` cases
- added fix for the case

Apparently, the `getParent(storeStack)` line in `storeChange` node defintion is needed to check, if store is created from combine or map, but the same check also led to `$store` as starting unit for `allSettled` being skipped in serialization - because there is no "parent trigger" 🤔 

Patch:
diff --git a/src/effector/fork/allSettled.ts b/src/effector/fork/allSettled.ts
index 66822520..f3f0097c 100644
--- a/src/effector/fork/allSettled.ts
+++ b/src/effector/fork/allSettled.ts
@@ -10,8 +10,8 @@ export function allSettled<T>(
 ) {
   if (!is.unit(start))
     return Promise.reject(new Error('first argument should be unit'))
-  if (!is.effect(start) && !is.event(start))
-    return Promise.reject(new Error('first argument accepts only effects and events'))
+  if (!is.effect(start) && !is.event(start) && !is.store(start))
+    return Promise.reject(new Error('first argument accepts only effects, events and stores'))
   const defer = createDefer()
   //@ts-expect-error
   defer.parentFork = forkPage
diff --git a/src/effector/fork/createScope.ts b/src/effector/fork/createScope.ts
index 057b61c6..1a677ce7 100644
--- a/src/effector/fork/createScope.ts
+++ b/src/effector/fork/createScope.ts
@@ -50,11 +50,12 @@ export function createScope(unit?: Domain): Scope {
     node: [
       calc((value, __, stack) => {
         const storeStack = getParent(stack)
-        if (storeStack && getParent(storeStack)) {
+        if (storeStack) {
           const storeNode = storeStack.node
           if (
             !getMeta(storeNode, 'isCombine') ||
-            getMeta(getParent(storeStack).node, 'op') !== 'combine'
+            (getParent(storeStack) &&
+              getMeta(getParent(storeStack).node, 'op') !== 'combine')
           ) {
             const forkPage = getForkPage(stack)!
             const id = storeNode.scope.state.id


Test patch:
diff --git a/src/effector/__tests__/fork/allSettled.test.ts b/src/effector/__tests__/fork/allSettled.test.ts
index ec0695ad..14ffb303 100644
--- a/src/effector/__tests__/fork/allSettled.test.ts
+++ b/src/effector/__tests__/fork/allSettled.test.ts
@@ -30,17 +30,14 @@ test('allSettled first argument validation', async () => {
   ).resolves.toBeUndefined()
 
   await expect(
-    // @ts-expect-error
-    allSettled(createStore(0), {scope: fork()}),
-  ).rejects.toThrowErrorMatchingInlineSnapshot(
-    `"first argument accepts only effects and events"`,
-  )
+    allSettled(createStore(0), {scope: fork(), params: 10}),
+  ).resolves.toBeUndefined()
 
   await expect(
     // @ts-expect-error
     allSettled(createDomain(), {scope: fork()}),
   ).rejects.toThrowErrorMatchingInlineSnapshot(
-    `"first argument accepts only effects and events"`,
+    `"first argument accepts only effects, events and stores"`,
   )
 })
 
@@ -63,6 +60,12 @@ describe('allSettled return value', () => {
     const result = await allSettled(event, {scope})
     expect(result).toBe(undefined)
   })
+  test('in case of store call', async () => {
+    const $store = createStore('value')
+    const scope = fork()
+    const result = await allSettled($store, {scope, params: 'value in scope'})
+    expect(result).toBe(undefined)
+  })
   describe('attach support', () => {
     test('throw in original effect', async () => {
       const original = createEffect<void, any>(async () =>
@@ -188,23 +191,49 @@ describe('transactions', () => {
     await promise2
     expect(serialize(scope1)).toMatchInlineSnapshot(`
       Object {
-        "-3nax9h": "a",
-        "qv6ij9": Array [
+        "7ps077": "a",
+        "sabdpu": Array [
           "a",
         ],
       }
     `)
     expect(serialize(scope2)).toMatchInlineSnapshot(`
       Object {
-        "-20x5sr": Array [
+        "-lsam6": Array [
           "b",
         ],
-        "-3nax9h": "b",
-        "-x0g8hr": "b",
-        "qv6ij9": Array [
+        "-vlbdb6": "b",
+        "7ps077": "b",
+        "sabdpu": Array [
           "b",
         ],
       }
     `)
   })
+  test('starting store is serialized', async () => {
+    const $store = createStore('value')
+    const scope = fork()
+
+    await allSettled($store, {scope, params: 'value in scope'})
+    expect(serialize(scope)).toMatchObject({
+      [$store.sid as string]: 'value in scope',
+    })
+    expect(scope.getState($store)).toEqual('value in scope')
+  })
+  test('starting store is serialized with correct value, if affected twice', async () => {
+    const $store = createStore('value')
+    sample({
+      source: $store,
+      filter: str => !str.includes('1'),
+      fn: str => str + '1',
+      target: $store,
+    })
+    const scope = fork()
+
+    await allSettled($store, {scope, params: 'value in scope'})
+    expect(serialize(scope)).toMatchObject({
+      [$store.sid as string]: 'value in scope1',
+    })
+    expect(scope.getState($store)).toEqual('value in scope1')
+  })
 })