Skip to content

Commit

Permalink
fix UrlStore constructor args
Browse files Browse the repository at this point in the history
  • Loading branch information
AkifumiSato committed Sep 13, 2023
1 parent e2f79e1 commit 7c229e7
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 51 deletions.
2 changes: 1 addition & 1 deletion packages/location-state-core/src/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function LocationStateProvider({
const [contextValue] = useState(() => ({
stores: {
session: new StorageStore(globalThis.sessionStorage),
url: new URLStore("location-state", syncer),
url: new URLStore(syncer),
},
}));

Expand Down
98 changes: 55 additions & 43 deletions packages/location-state-core/src/stores/url-store.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Syncer } from "../types";
import { URLStore } from "./url-store";
import { searchParamEncoder, URLStore } from "./url-store";

function prepareLocation({
pathname,
Expand Down Expand Up @@ -34,7 +34,7 @@ beforeEach(() => {

test("If params is empty, the initial value is undefined.", () => {
// Arrange
const store = new URLStore("store-key", syncerMock);
const store = new URLStore(syncerMock);
// Act
const slice = store.get("foo");
// Assert
Expand All @@ -47,14 +47,14 @@ test("On `set` called, store's values are updated and reflected in the URL.", ()
pathname: "/",
search: "?hoge=fuga",
});
const store = new URLStore("store-key", syncerMock);
const store = new URLStore(syncerMock);
// Act
store.set("foo", "updated");
// Assert
expect(store.get("foo")).toBe("updated");
expect(syncerMock.updateURL).toHaveBeenCalledTimes(1);
expect(syncerMock.updateURL).toHaveBeenCalledWith(
"http://localhost/?hoge=fuga&store-key=%7B%22foo%22%3A%22updated%22%7D",
"http://localhost/?hoge=fuga&location-state=%7B%22foo%22%3A%22updated%22%7D",
);
});

Expand All @@ -64,19 +64,22 @@ test("On `set` called with serializer, store's values are updated and reflected
pathname: "/",
search: "?hoge=fuga",
});
const store = new URLStore("store-key", syncerMock, {
serialize: () => "dummy-result",
deserialize: () => ({
foo: "not-used-value",
const store = new URLStore(
syncerMock,
searchParamEncoder("location-state", {
serialize: () => "dummy-result",
deserialize: () => ({
foo: "not-used-value",
}),
}),
});
);
// Act
store.set("foo", "updated");
// Assert
expect(store.get("foo")).toBe("updated");
expect(syncerMock.updateURL).toHaveBeenCalledTimes(1);
expect(syncerMock.updateURL).toHaveBeenCalledWith(
"http://localhost/?hoge=fuga&store-key=dummy-result",
"http://localhost/?hoge=fuga&location-state=dummy-result",
);
});

Expand All @@ -87,14 +90,17 @@ test("On `set` called with invalid serializer, store's values are initial value
pathname: "/",
search: "?hoge=fuga",
});
const store = new URLStore("store-key", syncerMock, {
serialize: () => {
throw new Error("serialize error");
},
deserialize: () => ({
foo: "not-used-value",
const store = new URLStore(
syncerMock,
searchParamEncoder("location-state", {
serialize: () => {
throw new Error("serialize error");
},
deserialize: () => ({
foo: "not-used-value",
}),
}),
});
);
// Act
store.set("foo", "updated");
// Assert
Expand All @@ -112,9 +118,9 @@ test("On `set` called with urlHandlers, store's values are updated and reflected
});
const encodeMock = jest.fn(
(state) =>
`${window.location.href}#mock-store-key=${JSON.stringify(state)}`,
`${window.location.href}#mock-location-state=${JSON.stringify(state)}`,
);
const store = new URLStore("store-key", syncerMock, undefined, {
const store = new URLStore(syncerMock, {
decode: () => ({}), // unused
encode: encodeMock,
});
Expand All @@ -124,14 +130,14 @@ test("On `set` called with urlHandlers, store's values are updated and reflected
expect(store.get("foo")).toBe("updated");
expect(syncerMock.updateURL).toHaveBeenCalledTimes(1);
expect(syncerMock.updateURL).toHaveBeenCalledWith(
'http://localhost/?hoge=fuga#mock-store-key={"foo":"updated"}',
'http://localhost/?hoge=fuga#mock-location-state={"foo":"updated"}',
);
expect(encodeMock).toHaveBeenCalledTimes(1);
});

test("listener is called when updating slice.", () => {
// Arrange
const store = new URLStore("store-key", syncerMock);
const store = new URLStore(syncerMock);
const listener = jest.fn();
store.subscribe("foo", listener);
// Act
Expand All @@ -142,7 +148,7 @@ test("listener is called when updating slice.", () => {

test("listener is called even if updated with undefined.", () => {
// Arrange
const store = new URLStore("store-key", syncerMock);
const store = new URLStore(syncerMock);
store.set("foo", "updated");
const listener = jest.fn();
store.subscribe("foo", listener);
Expand All @@ -155,7 +161,7 @@ test("listener is called even if updated with undefined.", () => {
test("store.get in the listener to get the latest value.", () => {
// Arrange
expect.assertions(4);
const store = new URLStore("store-key", syncerMock);
const store = new URLStore(syncerMock);
const listener1 = jest.fn(() => {
expect(store.get("foo")).toBe("updated");
});
Expand All @@ -173,7 +179,7 @@ test("store.get in the listener to get the latest value.", () => {

test("The listener is unsubscribed by the returned callback, it will no longer be called when the slice is updated.", () => {
// Arrange
const store = new URLStore("store-key", syncerMock);
const store = new URLStore(syncerMock);
const listeners = {
unsubscribeTarget: jest.fn(),
other: jest.fn(),
Expand All @@ -192,9 +198,9 @@ test("On `load` called, the state is loaded from url.", () => {
// Arrange
prepareLocation({
pathname: "/",
search: "?store-key=%7B%22foo%22%3A%22updated%22%7D",
search: "?location-state=%7B%22foo%22%3A%22updated%22%7D",
});
const store = new URLStore("store-key", syncerMock);
const store = new URLStore(syncerMock);
// Act
store.load();
// Assert
Expand All @@ -205,14 +211,17 @@ test("On `load` called with serializer, the value is obtained through serialize.
// Arrange
prepareLocation({
pathname: "/",
search: "?store-key=%7B%22foo%22%3A%22updated%22%7D",
search: "?location-state=%7B%22foo%22%3A%22updated%22%7D",
});
const store = new URLStore("store-key", syncerMock, {
serialize: () => "not-used-value",
deserialize: () => ({
foo: "dummy-result",
const store = new URLStore(
syncerMock,
searchParamEncoder("location-state", {
serialize: () => "not-used-value",
deserialize: () => ({
foo: "dummy-result",
}),
}),
});
);
// Act
store.load();
// Assert
Expand All @@ -224,14 +233,17 @@ test("On `load` called with invalid serializer, the value is initial value.", ()
const consoleSpy = jest.spyOn(console, "error").mockImplementation();
prepareLocation({
pathname: "/",
search: "?store-key=%7B%22foo%22%3A%22updated%22%7D",
});
const store = new URLStore("store-key", syncerMock, {
serialize: JSON.stringify,
deserialize: () => {
throw new Error("deserialize error");
},
search: "?location-state=%7B%22foo%22%3A%22updated%22%7D",
});
const store = new URLStore(
syncerMock,
searchParamEncoder("location-state", {
serialize: JSON.stringify,
deserialize: () => {
throw new Error("deserialize error");
},
}),
);
// Act
store.load();
// Assert
Expand All @@ -244,7 +256,7 @@ test("On `load` called with invalid serializer, the value is initial value.", ()

test("On `load` called, all listener notified.", async () => {
// Arrange
const store = new URLStore("store-key", syncerMock);
const store = new URLStore(syncerMock);
const listener1 = jest.fn();
const listener2 = jest.fn();
store.subscribe("foo", listener1);
Expand All @@ -263,9 +275,9 @@ test("On `load` called, delete parameter if invalid JSON string.", () => {
const consoleSpy = jest.spyOn(console, "error").mockImplementation();
prepareLocation({
pathname: "/",
search: "?store-key=invalid-json-string",
search: "?location-state=invalid-json-string",
});
const store = new URLStore("store-key", syncerMock);
const store = new URLStore(syncerMock);
// Act
store.load();
// Assert
Expand All @@ -284,7 +296,7 @@ test("On `load` called with urlHandlers, initial value depends on getter.", () =
const decodeMock = jest.fn(() => ({
foo: "initial-value",
}));
const store = new URLStore("store-key", syncerMock, undefined, {
const store = new URLStore(syncerMock, {
decode: decodeMock,
encode: () => "unused",
});
Expand Down
14 changes: 7 additions & 7 deletions packages/location-state-core/src/stores/url-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type URLEncoder = {
encode: (state?: Record<string, unknown>) => string;
};

function searchParamEncoder(
export function searchParamEncoder(
key: string,
stateSerializer: StateSerializer,
): URLEncoder {
Expand All @@ -29,18 +29,18 @@ function searchParamEncoder(
};
}

export const defaultSearchParamEncoder = searchParamEncoder(
"location-state",
jsonSerializer,
);

export class URLStore implements Store {
private state: Record<string, unknown> = {};
private readonly listeners: Map<string, Set<Listener>> = new Map();

constructor(
private readonly key: string,
private readonly syncer: Syncer,
private readonly stateSerializer: StateSerializer = jsonSerializer,
private readonly urlEncoder: URLEncoder = searchParamEncoder(
key,
stateSerializer,
),
private readonly urlEncoder: URLEncoder = defaultSearchParamEncoder,
) {}

subscribe(name: string, listener: Listener) {
Expand Down

0 comments on commit 7c229e7

Please sign in to comment.