Skip to content

Commit

Permalink
fix #1023: Suspense top level track, fix #1032 reconcile top level ke…
Browse files Browse the repository at this point in the history
…y change
  • Loading branch information
ryansolid committed Jun 8, 2022
1 parent 5f929b0 commit 17763fa
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 31 deletions.
4 changes: 2 additions & 2 deletions packages/solid/src/render/Suspense.ts
Expand Up @@ -172,7 +172,7 @@ export function Suspense(props: { fallback?: JSX.Element; children: JSX.Element
return (flicker = undefined);
}
if (ctx && p === undefined) setHydrateContext();
const rendered = untrack(() => props.children);
const rendered = createMemo(() => props.children);
return createMemo(() => {
const inFallback = store.inFallback(),
visibleContent = showContent ? showContent() : true,
Expand All @@ -182,7 +182,7 @@ export function Suspense(props: { fallback?: JSX.Element; children: JSX.Element
store.resolved = true;
ctx = p = undefined;
resumeEffects(store.effects);
return rendered;
return rendered();
}
if (!visibleFallback) return;
return createRoot(disposer => {
Expand Down
14 changes: 9 additions & 5 deletions packages/solid/store/src/modifiers.ts
@@ -1,6 +1,7 @@
import { batch } from "solid-js";
import { setProperty, unwrap, isWrappable, StoreNode, $RAW } from "./store";

const $ROOT = Symbol("store-root")

export type ReconcileOptions = {
key?: string | null;
merge?: boolean;
Expand All @@ -16,7 +17,10 @@ function applyState(
const previous = parent[property];
if (target === previous) return;
if (!isWrappable(target) || !isWrappable(previous) || (key && target[key] !== previous[key])) {
target !== previous && setProperty(parent, property, target);
if (target !== previous) {
if (property === $ROOT) return target;
setProperty(parent, property, target);
}
return;
}

Expand Down Expand Up @@ -103,7 +107,7 @@ function applyState(
}
}

// Diff method for setState
// Diff method for setStore
export function reconcile<T extends U, U>(
value: T,
options: ReconcileOptions = {}
Expand All @@ -112,8 +116,8 @@ export function reconcile<T extends U, U>(
v = unwrap(value);
return state => {
if (!isWrappable(state) || !isWrappable(v)) return v;
batch(() => applyState(v, { state }, "state", merge, key));
return state as T;
const res = applyState(v, { [$ROOT]: state }, $ROOT, merge, key);
return res === undefined ? state as T : res as T;
};
}

Expand Down
20 changes: 19 additions & 1 deletion packages/solid/store/src/server.ts
Expand Up @@ -29,6 +29,23 @@ function mergeStoreNode(state: any, value: any, force?: boolean) {
}
}

function updateArray(
current: any,
next: Array<any> | Record<string, any> | ((prev: any) => Array<any> | Record<string, any>)
) {
if (typeof next === "function") next = next(current);
if (Array.isArray(next)) {
if (current === next) return;
let i = 0,
len = next.length;
for (; i < len; i++) {
const value = next[i];
if (current[i] !== value) setProperty(current, i, value);
}
setProperty(current, "length", len);
} else mergeStoreNode(current, next);
}

export function updatePath(current: any, path: any[], traversed: PropertyKey[] = []) {
let part,
next = current;
Expand Down Expand Up @@ -75,8 +92,9 @@ export function updatePath(current: any, path: any[], traversed: PropertyKey[] =
}

export function createStore<T>(state: T | Store<T>): [Store<T>, SetStoreFunction<T>] {
const isArray = Array.isArray(state);
function setStore(...args: any[]): void {
updatePath(state, args);
isArray && args.length === 1 ? updateArray(state, args[0]) : updatePath(state, args);
}
return [state as Store<T>, setStore];
}
Expand Down
33 changes: 25 additions & 8 deletions packages/solid/store/test/modifiers.spec.ts
@@ -1,12 +1,5 @@
import { createRoot, createSignal, createEffect } from "../../src";
import {
createStore,
createMutable,
reconcile,
produce,
unwrap,
modifyMutable
} from "../src";
import { createStore, createMutable, reconcile, produce, unwrap, modifyMutable } from "../src";

describe("setState with reconcile", () => {
test("Reconcile a simple object", () => {
Expand Down Expand Up @@ -98,6 +91,30 @@ describe("setState with reconcile", () => {
expect(state.users[2].id).toBe(3);
expect(state.users[2].firstName).toBe("Brandon");
});

test("Reconcile top level key mismatch", () => {
const JOHN = { id: 1, firstName: "John", lastName: "Snow" },
NED = { id: 2, firstName: "Ned", lastName: "Stark" };

const [user, setUser] = createStore(JOHN);
expect(user.id).toBe(1);
expect(user.firstName).toBe("John");
setUser(reconcile(NED));
expect(user.id).toBe(2);
expect(user.firstName).toBe("Ned");
});

test("Reconcile nested top level key mismatch", () => {
const JOHN = { id: 1, firstName: "John", lastName: "Snow" },
NED = { id: 2, firstName: "Ned", lastName: "Stark" };

const [user, setUser] = createStore({ user: JOHN });
expect(user.user.id).toBe(1);
expect(user.user.firstName).toBe("John");
setUser("user", reconcile(NED));
expect(user.user.id).toBe(2);
expect(user.user.firstName).toBe("Ned");
});
});

describe("setState with produce", () => {
Expand Down
41 changes: 26 additions & 15 deletions packages/solid/web/test/suspense.spec.tsx
Expand Up @@ -3,18 +3,30 @@ import "../../test/MessageChannel";
import { lazy, createSignal, createResource, useTransition, enableScheduling } from "../../src";
import { render, Suspense, SuspenseList } from "../src";


global.queueMicrotask = setImmediate;
enableScheduling();

beforeEach(() => {
jest.useFakeTimers();
})
});
afterEach(() => {
jest.useRealTimers();
})
});
describe("Testing Basics", () => {
test("Children are reactive", () => {
let div = document.createElement("div");
let increment: () => void;
render(() => {
const [count, setCount] = createSignal(0);
increment = () => setCount(count() + 1);
return <Suspense>{count()}</Suspense>;
}, div);
expect(div.innerHTML).toBe("0");
increment!();
expect(div.innerHTML).toBe("1");
});
});
describe("Testing Suspense", () => {

let div = document.createElement("div"),
disposer: () => void,
resolvers: Function[] = [],
Expand All @@ -40,7 +52,7 @@ describe("Testing Suspense", () => {
expect(div.innerHTML).toBe("Loading");
});

test("Toggle Suspense control flow", async (done) => {
test("Toggle Suspense control flow", async done => {
for (const r of resolvers) r({ default: ChildComponent });

queueMicrotask(() => {
Expand All @@ -49,7 +61,7 @@ describe("Testing Suspense", () => {
});
});

test("Toggle with refresh transition", async (done) => {
test("Toggle with refresh transition", async done => {
const [pending, start] = useTransition();
let finished = false;

Expand All @@ -62,20 +74,20 @@ describe("Testing Suspense", () => {
expect(div.innerHTML).toBe("Hi, .Hello ");
expect(pending()).toBe(true);
expect(finished).toBe(false);

// Exhausts create-resource setTimeout
jest.runAllTimers();
// wait update suspence state
await Promise.resolve();
// wait update computation
// wait update computation
jest.runAllTicks();
jest.runAllTimers();
// wait write signal succ
queueMicrotask(()=>{
queueMicrotask(() => {
expect(div.innerHTML).toBe("Hi, Jo.Hello Jo");
expect(pending()).toBe(false);
expect(finished).toBe(true);
done()
done();
});
jest.runAllTicks();
});
Expand Down Expand Up @@ -159,11 +171,11 @@ describe("SuspenseList", () => {
jest.advanceTimersByTime(110);
await Promise.resolve();
expect(div.innerHTML).toBe("<div>Loading 1</div><div>Loading 2</div><div>Loading 3</div>");

jest.advanceTimersByTime(100);
await Promise.resolve();
expect(div.innerHTML).toBe("<div>A</div><div>B</div><div>Loading 3</div>");

jest.advanceTimersByTime(100);
await Promise.resolve();
expect(div.innerHTML).toBe("<div>A</div><div>B</div><div>C</div>");
Expand Down Expand Up @@ -191,12 +203,11 @@ describe("SuspenseList", () => {
jest.advanceTimersByTime(110);
await Promise.resolve();
expect(div.innerHTML).toBe("");

jest.advanceTimersByTime(100);
await Promise.resolve();
expect(div.innerHTML).toBe("<div>A</div><div>B</div>");



jest.advanceTimersByTime(100);
await Promise.resolve();
expect(div.innerHTML).toBe("<div>A</div><div>B</div><div>C</div>");
Expand Down

0 comments on commit 17763fa

Please sign in to comment.