Skip to content

Commit

Permalink
feat: expose previousValueStore for reactive bind
Browse files Browse the repository at this point in the history
  • Loading branch information
Holf committed Nov 3, 2023
1 parent 9f1a7fa commit 08b5c84
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 29 deletions.
3 changes: 2 additions & 1 deletion src/ReadableWithPrevious.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { Invalidator, Readable, Unsubscriber } from "svelte/store";

import { SubscriberWithPrevious } from "./SubscriberWithPrevious";
import { type SubscriberWithPrevious } from "./SubscriberWithPrevious";

export interface ReadableWithPrevious<T> extends Readable<T> {
subscribe: (
this: void,
run: SubscriberWithPrevious<T>,
invalidate?: Invalidator<T> | undefined,
) => Unsubscriber;
previousValueStore: Readable<T | undefined>;
}
7 changes: 4 additions & 3 deletions src/WritableWithPrevious.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { Invalidator, Unsubscriber, Writable } from "svelte/store";
import type { Invalidator, Readable, Unsubscriber, Writable } from "svelte/store";

import { ReadableWithPrevious } from "./ReadableWithPrevious";
import { SubscriberWithPrevious } from "./SubscriberWithPrevious";
import { type ReadableWithPrevious } from "./ReadableWithPrevious";
import { type SubscriberWithPrevious } from "./SubscriberWithPrevious";

export interface WritableWithPrevious<T> extends Writable<T>, ReadableWithPrevious<T> {
subscribe: (
this: void,
run: SubscriberWithPrevious<T>,
invalidate?: Invalidator<T> | undefined,
) => Unsubscriber;
previousValueStore: Readable<T | undefined>;
}
12 changes: 6 additions & 6 deletions src/getPrevious.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Writable, derived, get, writable } from "svelte/store";
import { type Writable, derived, get, writable } from "svelte/store";
import { expect, describe, it, beforeEach } from "vitest";

import { getPrevious } from "./getPrevious";
import { giveSvelteStorePreviousBehaviour } from "./giveSvelteStorePreviousBehaviour";
import { ReadableWithPrevious } from "./ReadableWithPrevious";
import { WritableWithPrevious } from "./WritableWithPrevious";
import { type ReadableWithPrevious } from "./ReadableWithPrevious";
import { type WritableWithPrevious } from "./WritableWithPrevious";

describe("getPrevious", () => {
const firstValue = Symbol("firstValue");
Expand All @@ -19,7 +19,7 @@ describe("getPrevious", () => {

describe("initialised with a value", () => {
it("`getPrevious` should return undefined", () => {
expect(getPrevious(writableWithPrevious)).toBe(undefined);
expect(getPrevious(writableWithPrevious)).toBeUndefined();
});

it("`get` should return the value with which the store was initialised", () => {
Expand Down Expand Up @@ -55,7 +55,7 @@ describe("getPrevious", () => {

describe("initialised with a value", () => {
it("`getPrevious` should return undefined", () => {
expect(getPrevious(readableWithPrevious)).toBe(undefined);
expect(getPrevious(readableWithPrevious)).toBeUndefined();
});

it("`get` should return the value with which the store was initialised", () => {
Expand Down Expand Up @@ -86,7 +86,7 @@ describe("getPrevious", () => {
// anyway, for those who ignore or don't see type errors.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
expect(getPrevious(writableStore)).toBe(undefined);
expect(getPrevious(writableStore)).toBeUndefined();
});
});
});
2 changes: 1 addition & 1 deletion src/getPrevious.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ReadableWithPrevious } from "./index";
import { type ReadableWithPrevious } from "./index";

export function getPrevious<T>(store: ReadableWithPrevious<T>) {
let value: T | undefined;
Expand Down
105 changes: 100 additions & 5 deletions src/giveSvelteStorePreviousBehaviour.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
import { writable } from "svelte/store";
import { expect, describe, it, beforeEach } from "vitest";
// eslint-disable-next-line import/no-duplicates
import type * as svelteStore from "svelte/store";
// eslint-disable-next-line import/no-duplicates
import { get, type Readable, writable, derived } from "svelte/store";
import { expect, describe, it, beforeEach, vi, type Mock } from "vitest";

import { giveSvelteStorePreviousBehaviour } from "./giveSvelteStorePreviousBehaviour";
import { WritableWithPrevious } from "./WritableWithPrevious";
import { type WritableWithPrevious } from "./WritableWithPrevious";

describe("giveSvelteStorePreviousBehaviour", () => {
vi.mock("svelte/store", async () => {
const actual: typeof svelteStore = await vi.importActual("svelte/store");
return {
...actual,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
derived: vi.fn((...args) => (actual.derived as any)(...args)),
};
});

const derivedSpy = derived as Mock;

describe("store processed with `giveSvelteStorePreviousBehaviour`", () => {
describe("Unaltered store functions", () => {
const writableStore = writable(Symbol("storeValue"));
const writableWithPrevious = giveSvelteStorePreviousBehaviour(writableStore);
Expand Down Expand Up @@ -43,7 +57,7 @@ describe("giveSvelteStorePreviousBehaviour", () => {
});

it("and `previousValue` should be undefined", () => {
expect(previous).toBe(undefined);
expect(previous).toBeUndefined();
});
});

Expand All @@ -68,4 +82,85 @@ describe("giveSvelteStorePreviousBehaviour", () => {
});
});
});

describe("`previousValueStore` property", () => {
const firstValue = Symbol("firstValue");
const secondValue = Symbol("secondValue");

let writableWithPrevious: WritableWithPrevious<symbol>;
let previousValueStore: Readable<symbol | undefined>;

describe("`previousValueStore` property accessed immediately", () => {
beforeEach(() => {
writableWithPrevious = giveSvelteStorePreviousBehaviour(writable(firstValue));
({ previousValueStore } = writableWithPrevious);
});

it("should be a 'derived' store", () => {
expect(previousValueStore).toEqual({ subscribe: expect.any(Function) });
});

describe("store initialised with a value", () => {
it("`previousValueStore` value should be undefined", () => {
expect(get(previousValueStore)).toBeUndefined();
});
});

describe("store initialised with a value and then `set` to a new value", () => {
beforeEach(() => {
writableWithPrevious.set(secondValue);
});

it("`previousValueStore` value should be the initial value", () => {
expect(get(previousValueStore)).toBe(firstValue);
});
});
});

describe("`previousValueStore` property accessed after parent store has had value changes", () => {
describe("Store is created with value of one, then given Previous Behaviour and then `set` to value two", () => {
beforeEach(() => {
writableWithPrevious = giveSvelteStorePreviousBehaviour(writable(firstValue));
writableWithPrevious.set(secondValue);
});

describe("`previousValueStore` is then instantiated", () => {
beforeEach(() => {
({ previousValueStore } = writableWithPrevious);
});

it("`previousValueStore` value should be value one", () => {
expect(get(previousValueStore)).toBe(firstValue);
});
});
});
});

describe("Lazy instantiation of `previousValueStore`", () => {
beforeEach(() => {
derivedSpy.mockReset();
});

describe("Giving store Previous Behaviour", () => {
beforeEach(() => {
writableWithPrevious = giveSvelteStorePreviousBehaviour(writable(firstValue));
});

it("should not cause `previousValueStore` to be instantiated", () => {
expect(derivedSpy).not.toHaveBeenCalled();
});
});

describe("Getting `previousValueStore` from store with Previous Behaviour", () => {
beforeEach(() => {
({ previousValueStore } = writableWithPrevious);
console.log(previousValueStore);
});

it("should cause it to be instantiated", () => {
expect(derivedSpy).toHaveBeenCalled();
});
});
});
});
});
29 changes: 19 additions & 10 deletions src/giveSvelteStorePreviousBehaviour.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,44 @@
import type { Readable, Writable } from "svelte/store";
import { derived, type Readable, type Writable } from "svelte/store";

import { ReadableWithPrevious } from "./ReadableWithPrevious";
import { SubscriberWithPrevious } from "./SubscriberWithPrevious";
import { WritableWithPrevious } from "./WritableWithPrevious";
import { type ReadableWithPrevious } from "./ReadableWithPrevious";
import { type SubscriberWithPrevious } from "./SubscriberWithPrevious";
import { type WritableWithPrevious } from "./WritableWithPrevious";

export function giveSvelteStorePreviousBehaviour<T>(store: Writable<T>): WritableWithPrevious<T>;
export function giveSvelteStorePreviousBehaviour<T>(store: Readable<T>): ReadableWithPrevious<T>;

export function giveSvelteStorePreviousBehaviour<T>(
store: Readable<T> | Writable<T>,
): ReadableWithPrevious<T> | WritableWithPrevious<T> {
const { subscribe: originalSubscribe, ...rest } = store;
const { subscribe, ...rest } = store;

const storeValues: { current: undefined | T; previous: undefined | T } = {
current: undefined,
previous: undefined,
};

originalSubscribe((x) => {
subscribe((x) => {
storeValues.previous = storeValues.current;
storeValues.current = x;
});

const forReturn = {
let _previousValueStore: Readable<T | undefined>;

return {
...rest,

subscribe(run: SubscriberWithPrevious<T>) {
return originalSubscribe((value: T) => {
return subscribe((value: T) => {
run(value, storeValues.previous);
});
},
};

return forReturn;
get previousValueStore() {
if (!_previousValueStore) {
_previousValueStore = derived(store, () => storeValues.previous);
}

return _previousValueStore;
},
};
}
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { giveSvelteStorePreviousBehaviour } from "./giveSvelteStorePreviousBehaviour";
export { getPrevious } from "./getPrevious";
export { ReadableWithPrevious } from "./ReadableWithPrevious";
export { WritableWithPrevious } from "./WritableWithPrevious";
export { SubscriberWithPrevious } from "./SubscriberWithPrevious";
export { type ReadableWithPrevious } from "./ReadableWithPrevious";
export { type WritableWithPrevious } from "./WritableWithPrevious";
export { type SubscriberWithPrevious } from "./SubscriberWithPrevious";

0 comments on commit 08b5c84

Please sign in to comment.