Skip to content

Commit

Permalink
Merge pull request #12 from effector/fix-client-detection
Browse files Browse the repository at this point in the history
Fix client detection and add tests for that
  • Loading branch information
AlexandrHoroshih authored May 7, 2023
2 parents 8b06729 + a1e2aaf commit 3611670
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 123 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@vitejs/plugin-react": "^3.1.0",
"effector": "^22.8.1",
"effector-react": "^22.5.1",
"happy-dom": "^9.10.9",
"react": "^18.2.0",
"typescript": "^5.0.3",
"vite": "^4.2.1",
Expand Down
55 changes: 53 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

133 changes: 133 additions & 0 deletions src/get-scope.browser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// @vitest-environment happy-dom

import { describe, test, expect } from "vitest";
import {
createStore,
createEvent,
createEffect,
fork,
serialize,
allSettled,
combine,
sample,
} from "effector";

import { getScope } from "./get-scope";

const up = createEvent();
const longUpFx = createEffect(async () => {
await new Promise((r) => setTimeout(r, 10));
});
const $count = createStore(0).on([up, longUpFx.done], (s) => s + 1);
const $derived = $count.map((s) => ({ ref: s }));
const $combined = combine({ ref: $count });
const $nestedCombined = combine({ ref: $derived });

const $sampled = sample({
source: { ref: $combined },
fn: (ref) => ref.ref.ref,
});

const getFixedDate = () => new Date(0);
const updateDate = createEvent<Date>();
const $specialData = createStore(getFixedDate(), {
serialize: {
write: (_date) => ({ lol: "jsonified view" }),
read: (_json) => getFixedDate(),
},
}).on($count, () => getFixedDate());

describe("getClientScope", () => {
test("should handle server values injection on the fly", async () => {
const serverScope = fork();

await allSettled(up, { scope: serverScope });
await allSettled(up, { scope: serverScope });
await allSettled(up, { scope: serverScope });

const serverValues = serialize(serverScope);

const clientScopeOne = getScope();

expect(clientScopeOne.getState($count)).toEqual(0);
expect(clientScopeOne.getState($derived)).toEqual({ ref: 0 });
expect(clientScopeOne.getState($combined)).toEqual({ ref: 0 });
expect(clientScopeOne.getState($nestedCombined)).toEqual({
ref: { ref: 0 },
});
expect(clientScopeOne.getState($sampled)).toEqual(0);
expect(clientScopeOne.getState(longUpFx.pending)).toEqual(false);
expect(clientScopeOne.getState(longUpFx.inFlight)).toEqual(0);
expect(clientScopeOne.getState($specialData)).toEqual(getFixedDate());

const promise = allSettled(longUpFx, { scope: clientScopeOne });

expect(clientScopeOne.getState(longUpFx.inFlight)).toEqual(1);

const clientScopeTwo = getScope(serverValues);

expect(clientScopeTwo.getState($count)).toEqual(3);
expect(clientScopeOne.getState($derived)).toEqual({ ref: 3 });
expect(clientScopeOne.getState($combined)).toEqual({ ref: 3 });
expect(clientScopeOne.getState($nestedCombined)).toEqual({
ref: { ref: 3 },
});
expect(clientScopeOne.getState($sampled)).toEqual(3);
expect(clientScopeOne.getState(longUpFx.pending)).toEqual(true);
expect(clientScopeOne.getState(longUpFx.inFlight)).toEqual(1);
expect(clientScopeOne.getState($specialData)).toEqual(getFixedDate());

await promise;

expect(clientScopeTwo.getState($count)).toEqual(4);
expect(clientScopeOne.getState($derived)).toEqual({ ref: 4 });
expect(clientScopeOne.getState($combined)).toEqual({ ref: 4 });
expect(clientScopeOne.getState($nestedCombined)).toEqual({
ref: { ref: 4 },
});
expect(clientScopeOne.getState($sampled)).toEqual(4);
expect(clientScopeOne.getState(longUpFx.pending)).toEqual(false);
expect(clientScopeOne.getState(longUpFx.inFlight)).toEqual(0);
expect(clientScopeOne.getState($specialData)).toEqual(getFixedDate());
});
test("shallow navigation to same page", async () => {
const serverScope = fork();

await allSettled(up, { scope: serverScope });
await allSettled(up, { scope: serverScope });
await allSettled(up, { scope: serverScope });

const values = serialize(serverScope);

const clientScopeOne = getScope(values);

expect(clientScopeOne.getState($count)).toEqual(3);

await allSettled(up, { scope: clientScopeOne });

expect(clientScopeOne.getState($count)).toEqual(4);

// This imitates shallow navigation to same page, e.g. with different query params
//
// Next.js will reuse the same pageProps instance in this case
// which will basically override current page state with initial one
//
// So we need to basically just ignore it, because
// we already have the latest state in the client scope
const clientScopeTwo = getScope(values);

expect(clientScopeTwo.getState($count)).toEqual(4);
});
});

describe("getScope implementation details", () => {
test("should return same scope on client every time", () => {
/**
* Implementation detail that may change in the future
*/
const scopeOne = getScope();
const scopeTwo = getScope();

expect(scopeOne === scopeTwo).toBe(true);
});
});
126 changes: 11 additions & 115 deletions src/get-scope.test.ts
Original file line number Diff line number Diff line change
@@ -1,119 +1,15 @@
import { describe, test, expect } from "vitest";
import {
createStore,
createEvent,
createEffect,
fork,
serialize,
allSettled,
combine,
sample,
} from "effector";

import { internalGetClientScope } from "./get-scope";
import { getScope } from "./get-scope";

const up = createEvent();
const longUpFx = createEffect(async () => {
await new Promise((r) => setTimeout(r, 10));
});
const $count = createStore(0).on([up, longUpFx.done], (s) => s + 1);
const $derived = $count.map((s) => ({ ref: s }));
const $combined = combine({ ref: $count });
const $nestedCombined = combine({ ref: $derived });
describe('getScope implementation details', () => {
test('should return new scope on server every time', () => {
/**
* Implementation detail that may change in the future
*/
const scopeOne = getScope();
const scopeTwo = getScope();

const $sampled = sample({
source: { ref: $combined },
fn: (ref) => ref.ref.ref,
});

const getFixedDate = () => new Date(0);
const updateDate = createEvent<Date>();
const $specialData = createStore(getFixedDate(), {
serialize: {
write: (_date) => ({ lol: "jsonified view" }),
read: (_json) => getFixedDate(),
},
}).on($count, () => getFixedDate());

describe("getClientScope", () => {
test("should handle server values injection on the fly", async () => {
const serverScope = fork();

await allSettled(up, { scope: serverScope });
await allSettled(up, { scope: serverScope });
await allSettled(up, { scope: serverScope });

const serverValues = serialize(serverScope);

const clientScopeOne = internalGetClientScope();

expect(clientScopeOne.getState($count)).toEqual(0);
expect(clientScopeOne.getState($derived)).toEqual({ ref: 0 });
expect(clientScopeOne.getState($combined)).toEqual({ ref: 0 });
expect(clientScopeOne.getState($nestedCombined)).toEqual({
ref: { ref: 0 },
});
expect(clientScopeOne.getState($sampled)).toEqual(0);
expect(clientScopeOne.getState(longUpFx.pending)).toEqual(false);
expect(clientScopeOne.getState(longUpFx.inFlight)).toEqual(0);
expect(clientScopeOne.getState($specialData)).toEqual(getFixedDate());

const promise = allSettled(longUpFx, { scope: clientScopeOne });

expect(clientScopeOne.getState(longUpFx.inFlight)).toEqual(1);

const clientScopeTwo = internalGetClientScope(serverValues);

expect(clientScopeTwo.getState($count)).toEqual(3);
expect(clientScopeOne.getState($derived)).toEqual({ ref: 3 });
expect(clientScopeOne.getState($combined)).toEqual({ ref: 3 });
expect(clientScopeOne.getState($nestedCombined)).toEqual({
ref: { ref: 3 },
});
expect(clientScopeOne.getState($sampled)).toEqual(3);
expect(clientScopeOne.getState(longUpFx.pending)).toEqual(true);
expect(clientScopeOne.getState(longUpFx.inFlight)).toEqual(1);
expect(clientScopeOne.getState($specialData)).toEqual(getFixedDate());

await promise;

expect(clientScopeTwo.getState($count)).toEqual(4);
expect(clientScopeOne.getState($derived)).toEqual({ ref: 4 });
expect(clientScopeOne.getState($combined)).toEqual({ ref: 4 });
expect(clientScopeOne.getState($nestedCombined)).toEqual({
ref: { ref: 4 },
});
expect(clientScopeOne.getState($sampled)).toEqual(4);
expect(clientScopeOne.getState(longUpFx.pending)).toEqual(false);
expect(clientScopeOne.getState(longUpFx.inFlight)).toEqual(0);
expect(clientScopeOne.getState($specialData)).toEqual(getFixedDate());
});
test("shallow navigation to same page", async () => {
const serverScope = fork();

await allSettled(up, { scope: serverScope });
await allSettled(up, { scope: serverScope });
await allSettled(up, { scope: serverScope });

const values = serialize(serverScope);

const clientScopeOne = internalGetClientScope(values);

expect(clientScopeOne.getState($count)).toEqual(3);

await allSettled(up, { scope: clientScopeOne });

expect(clientScopeOne.getState($count)).toEqual(4);

// This imitates shallow navigation to same page, e.g. with different query params
//
// Next.js will reuse the same pageProps instance in this case
// which will basically override current page state with initial one
//
// So we need to basically just ignore it, because
// we already have the latest state in the client scope
const clientScopeTwo = internalGetClientScope(values);

expect(clientScopeTwo.getState($count)).toEqual(4);
});
});
expect(scopeOne !== scopeTwo).toBe(true);
})
})
Loading

0 comments on commit 3611670

Please sign in to comment.