Skip to content

Commit

Permalink
createCache and createIntervalCache subscribeToStatus API renamed to …
Browse files Browse the repository at this point in the history
…subscribe
  • Loading branch information
bvaughn committed Jun 28, 2023
1 parent dc4d98a commit f6a36d5
Show file tree
Hide file tree
Showing 13 changed files with 484 additions and 332 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
"@babel/preset-typescript": "^7.21.5",
"@parcel/config-default": "^2.9.3",
"@parcel/core": "^2.9.3",
"@parcel/packager-ts": "2.9.2",
"@parcel/packager-ts": "^2.9.3",
"@parcel/transformer-js": "^2.9.3",
"@parcel/transformer-react-refresh-wrap": "^2.9.3",
"@parcel/transformer-typescript-types": "2.9.2",
"@parcel/transformer-typescript-types": "^2.9.3",
"@preconstruct/cli": "^2.7.0",
"@types/jest": "^29.4.0",
"@types/node": "^18.14.6",
Expand Down
5 changes: 5 additions & 0 deletions packages/suspense/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 0.0.44
* `createCache` and `createIntervalCache` methods `subscribeToStatus` renamed to `subscribe` and parameters changed to also include value or error
* `createCache` subscribers notified after value explicitly cached via `cache`
* `useCacheMutation` sync mutation notifies subscribers after mutation

## 0.0.43
* Fix potential update loop in `useImperativeCacheValue` from nested object properties

Expand Down
135 changes: 93 additions & 42 deletions packages/suspense/src/cache/createCache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ describe("createCache", () => {
});
});

describe("subscribeToStatus", () => {
describe("subscribe", () => {
let callbackA: jest.Mock;
let callbackB: jest.Mock;

Expand All @@ -376,72 +376,84 @@ describe("createCache", () => {
});

it("should subscribe to keys that have not been loaded", async () => {
cache.subscribeToStatus(callbackA, "sync");
cache.subscribe(callbackA, "sync");

expect(callbackA).toHaveBeenCalledTimes(1);
expect(callbackA).toHaveBeenCalledWith(STATUS_NOT_FOUND);
expect(callbackA).toHaveBeenCalledWith({ status: STATUS_NOT_FOUND });

await Promise.resolve();

expect(callbackA).toHaveBeenCalledTimes(1);
});

it("should notify of the transition from undefined to resolved for synchronous caches", async () => {
cache.subscribeToStatus(callbackA, "sync");
cache.subscribe(callbackA, "sync");

expect(callbackA).toHaveBeenCalledTimes(1);
expect(callbackA).toHaveBeenCalledWith(STATUS_NOT_FOUND);
expect(callbackA).toHaveBeenCalledWith({ status: STATUS_NOT_FOUND });

cache.readAsync("sync");

expect(callbackA).toHaveBeenCalledTimes(3);
expect(callbackA).toHaveBeenCalledWith(STATUS_PENDING);
expect(callbackA).toHaveBeenCalledWith(STATUS_RESOLVED);
expect(callbackA).toHaveBeenCalledWith({ status: STATUS_PENDING });
expect(callbackA).toHaveBeenCalledWith({
status: STATUS_RESOLVED,
value: "sync",
});
});

it("should notify of the transition from undefined to pending to resolved for async caches", async () => {
cache.subscribeToStatus(callbackA, "async");
cache.subscribe(callbackA, "async");

expect(callbackA).toHaveBeenCalledTimes(1);
expect(callbackA).toHaveBeenCalledWith(STATUS_NOT_FOUND);
expect(callbackA).toHaveBeenCalledWith({ status: STATUS_NOT_FOUND });

const thenable = cache.readAsync("async");

expect(callbackA).toHaveBeenCalledTimes(2);
expect(callbackA).toHaveBeenCalledWith(STATUS_PENDING);
expect(callbackA).toHaveBeenCalledWith({ status: STATUS_PENDING });

await thenable;

expect(callbackA).toHaveBeenCalledTimes(3);
expect(callbackA).toHaveBeenCalledWith(STATUS_RESOLVED);
expect(callbackA).toHaveBeenCalledWith({
status: STATUS_RESOLVED,
value: "async",
});
});

it("should only notify each subscriber once", async () => {
cache.subscribeToStatus(callbackA, "sync");
cache.subscribeToStatus(callbackB, "sync");
cache.subscribe(callbackA, "sync");
cache.subscribe(callbackB, "sync");

expect(callbackA).toHaveBeenCalledTimes(1);
expect(callbackA).toHaveBeenCalledWith(STATUS_NOT_FOUND);
expect(callbackA).toHaveBeenCalledWith({ status: STATUS_NOT_FOUND });

expect(callbackB).toHaveBeenCalledTimes(1);
expect(callbackB).toHaveBeenCalledWith(STATUS_NOT_FOUND);
expect(callbackB).toHaveBeenCalledWith({ status: STATUS_NOT_FOUND });

cache.readAsync("sync");

expect(callbackA).toHaveBeenCalledTimes(3);
expect(callbackA).toHaveBeenCalledWith(STATUS_PENDING);
expect(callbackA).toHaveBeenCalledWith(STATUS_RESOLVED);
expect(callbackA).toHaveBeenCalledWith({ status: STATUS_PENDING });
expect(callbackA).toHaveBeenCalledWith({
status: STATUS_RESOLVED,
value: "sync",
});

expect(callbackB).toHaveBeenCalledTimes(3);
expect(callbackB).toHaveBeenCalledWith(STATUS_PENDING);
expect(callbackB).toHaveBeenCalledWith(STATUS_RESOLVED);
expect(callbackB).toHaveBeenCalledWith({ status: STATUS_PENDING });
expect(callbackB).toHaveBeenCalledWith({
status: STATUS_RESOLVED,
value: "sync",
});
});

it("should not notify after a subscriber unsubscribes", async () => {
const unsubscribe = cache.subscribeToStatus(callbackA, "sync");
const unsubscribe = cache.subscribe(callbackA, "sync");

expect(callbackA).toHaveBeenCalledTimes(1);
expect(callbackA).toHaveBeenCalledWith(STATUS_NOT_FOUND);
expect(callbackA).toHaveBeenCalledWith({ status: STATUS_NOT_FOUND });

unsubscribe();

Expand All @@ -451,8 +463,8 @@ describe("createCache", () => {
});

it("should track subscribers separately, per key", async () => {
cache.subscribeToStatus(callbackA, "sync-1");
cache.subscribeToStatus(callbackB, "sync-2");
cache.subscribe(callbackA, "sync-1");
cache.subscribe(callbackB, "sync-2");

callbackA.mockClear();
callbackB.mockClear();
Expand All @@ -464,8 +476,8 @@ describe("createCache", () => {
});

it("should track unsubscriptions separately, per key", async () => {
const unsubscribeA = cache.subscribeToStatus(callbackA, "sync-1");
cache.subscribeToStatus(callbackB, "sync-2");
const unsubscribeA = cache.subscribe(callbackA, "sync-1");
cache.subscribe(callbackB, "sync-2");

callbackA.mockClear();
callbackB.mockClear();
Expand All @@ -487,11 +499,17 @@ describe("createCache", () => {

await Promise.resolve();

cache.subscribeToStatus(callbackA, "async");
cache.subscribeToStatus(callbackB, "error");
cache.subscribe(callbackA, "async");
cache.subscribe(callbackB, "error");

expect(callbackA).toHaveBeenCalledWith(STATUS_RESOLVED);
expect(callbackB).toHaveBeenCalledWith(STATUS_REJECTED);
expect(callbackA).toHaveBeenCalledWith({
status: STATUS_RESOLVED,
value: "async",
});
expect(callbackB).toHaveBeenCalledWith({
error: "error",
status: STATUS_REJECTED,
});
});

it("should notify subscribers after a value is evicted", async () => {
Expand All @@ -500,20 +518,25 @@ describe("createCache", () => {

await Promise.resolve();

cache.subscribeToStatus(callbackA, "sync-1");
cache.subscribeToStatus(callbackB, "sync-2");
cache.subscribe(callbackA, "sync-1");
cache.subscribe(callbackB, "sync-2");

expect(callbackA).toHaveBeenCalledTimes(1);
expect(callbackA).toHaveBeenCalledWith(STATUS_RESOLVED);
expect(callbackA).toHaveBeenCalledWith({
status: STATUS_RESOLVED,
value: "sync-1",
});
expect(callbackB).toHaveBeenCalledTimes(1);
expect(callbackB).toHaveBeenCalledWith(STATUS_RESOLVED);
expect(callbackB).toHaveBeenCalledWith({
status: STATUS_RESOLVED,
value: "sync-2",
});

cache.evict("sync-1");

expect(callbackA).toHaveBeenCalledTimes(2);
expect(callbackA).toHaveBeenCalledWith(STATUS_NOT_FOUND);
expect(callbackA).toHaveBeenCalledWith({ status: STATUS_NOT_FOUND });
expect(callbackB).toHaveBeenCalledTimes(1);
expect(callbackB).toHaveBeenCalledWith(STATUS_RESOLVED);
});

it("should notify subscribers after all values are evicted", async () => {
Expand All @@ -522,20 +545,48 @@ describe("createCache", () => {

await Promise.resolve();

cache.subscribeToStatus(callbackA, "sync-1");
cache.subscribeToStatus(callbackB, "sync-2");
cache.subscribe(callbackA, "sync-1");
cache.subscribe(callbackB, "sync-2");

expect(callbackA).toHaveBeenCalledTimes(1);
expect(callbackA).toHaveBeenCalledWith(STATUS_RESOLVED);
expect(callbackA).toHaveBeenCalledWith({
status: STATUS_RESOLVED,
value: "sync-1",
});
expect(callbackB).toHaveBeenCalledTimes(1);
expect(callbackB).toHaveBeenCalledWith(STATUS_RESOLVED);
expect(callbackB).toHaveBeenCalledWith({
status: STATUS_RESOLVED,
value: "sync-2",
});

cache.evictAll();

expect(callbackA).toHaveBeenCalledTimes(2);
expect(callbackA).toHaveBeenCalledWith(STATUS_NOT_FOUND);
expect(callbackA).toHaveBeenCalledWith({ status: STATUS_NOT_FOUND });
expect(callbackB).toHaveBeenCalledTimes(2);
expect(callbackB).toHaveBeenCalledWith(STATUS_NOT_FOUND);
expect(callbackB).toHaveBeenCalledWith({ status: STATUS_NOT_FOUND });
});

it("should notify subscribers when an object is externally cached", () => {
const cache = createCache<[string], Object>({
debugLabel: "cache",
load: async () => {},
});

cache.subscribe(callbackA, "externally-managed");

expect(callbackA).toHaveBeenCalledWith({ status: STATUS_NOT_FOUND });

const value = { id: 123 };

cache.cache(value, "externally-managed");

expect(callbackA).toHaveBeenCalledTimes(2);
expect(callbackA).toHaveBeenCalledWith({
status: STATUS_RESOLVED,
value,
});
expect(callbackA.mock.lastCall[0].value).toEqual(value);
});
});

Expand Down Expand Up @@ -765,7 +816,7 @@ describe("createCache", () => {
getKey: getCacheKey,
load,
});
console.log(consoleMock.mock.calls);

expect(consoleMock).toHaveBeenCalled();
expect(consoleMock.mock.calls[0]).toEqual(
expect.arrayContaining([
Expand Down
Loading

0 comments on commit f6a36d5

Please sign in to comment.