-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from recruit-tech/setup
Migrate core implementation and testing
- Loading branch information
Showing
35 changed files
with
5,360 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# dependencies | ||
/**/node_modules | ||
/.pnp | ||
.pnp.js | ||
|
||
# testing | ||
/**/coverage | ||
|
||
# distribution | ||
/**/dist | ||
|
||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
.pnpm-debug.log* | ||
|
||
# local env files | ||
.env*.local | ||
|
||
# typescript | ||
*.tsbuildinfo | ||
|
||
# turbo | ||
.turbo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import "@testing-library/jest-dom"; | ||
import "@testing-library/jest-dom/extend-expect"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/** @type {import('jest').Config} */ | ||
const config = { | ||
testEnvironment: 'jest-environment-jsdom', | ||
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'], | ||
transform: { | ||
'^.+\\.tsx?$': 'ts-jest', | ||
}, | ||
} | ||
|
||
export default config |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"name": "@location-state/core", | ||
"main": "./src/index.ts", | ||
"types": "./src/index.ts", | ||
"scripts": { | ||
"build": "tsup src/index.ts", | ||
"typecheck": "tsc --noEmit", | ||
"test": "jest" | ||
}, | ||
"devDependencies": { | ||
"@testing-library/jest-dom": "5.17.0", | ||
"@testing-library/react": "14.0.0", | ||
"@testing-library/user-event": "14.4.3", | ||
"@types/testing-library__jest-dom": "5.14.9", | ||
"@types/uuid": "9.0.2", | ||
"client-only": "0.0.1", | ||
"jest-environment-jsdom": "29.6.3", | ||
"test-utils": "workspace:*", | ||
"uuid": "9.0.0" | ||
}, | ||
"peerDependencies": { | ||
"react": "^18.2.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { Store } from "./stores/types"; | ||
import { createContext } from "react"; | ||
|
||
export const LocationStateContext = createContext<{ | ||
stores: Record<string, Store>; | ||
}>({ stores: {} }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { LocationStateContext } from "./context"; | ||
import { StoreName } from "./types"; | ||
import { useCallback, useContext, useSyncExternalStore } from "react"; | ||
|
||
export const useLocationState = <T>({ | ||
name, | ||
defaultValue, | ||
storeName, | ||
}: { | ||
name: string; | ||
defaultValue: T; | ||
storeName: StoreName | string; | ||
}): [T, (value: T) => void] => { | ||
const { stores } = useContext(LocationStateContext); | ||
const store = stores[storeName]; | ||
if (!store) { | ||
// todo: fix message | ||
throw new Error("Provider is required"); | ||
} | ||
const subscribe = useCallback( | ||
(onStoreChange: () => void) => store.subscribe(name, onStoreChange), | ||
[name, store], | ||
); | ||
// `defaultValue` is assumed to always be the same value (for Objects, it must be memoized). | ||
const storeState = useSyncExternalStore( | ||
subscribe, | ||
() => (store.get(name) as T) ?? defaultValue, | ||
() => defaultValue, | ||
); | ||
const setStoreState = useCallback( | ||
// todo: accept functions like useState | ||
(value: T) => { | ||
store.set(name, value); | ||
}, | ||
[name, store], | ||
); | ||
return [storeState, setStoreState]; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export * from './hooks' | ||
export * from './provider' | ||
export * from './types' | ||
export * from './syncers' | ||
export * from './unsafe-navigation' | ||
export * from './stores' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { useLocationState } from "./hooks"; | ||
import { LocationStateProvider } from "./provider"; | ||
import { createNavigationMock } from "test-utils"; | ||
import { renderWithUser } from "test-utils"; | ||
import { screen, waitFor } from "@testing-library/react"; | ||
|
||
function LocationSyncCounter() { | ||
const [counter, setCounter] = useLocationState({ | ||
name: "counter", | ||
defaultValue: 0, | ||
storeName: "session", | ||
}); | ||
return ( | ||
<div> | ||
<h1>counter: {counter}</h1> | ||
<button onClick={() => setCounter(counter + 1)}>increment</button> | ||
</div> | ||
); | ||
} | ||
|
||
function LocationSyncCounterPage() { | ||
return ( | ||
<LocationStateProvider> | ||
<LocationSyncCounter /> | ||
</LocationStateProvider> | ||
); | ||
} | ||
|
||
const mockNavigation = createNavigationMock("/"); | ||
// @ts-ignore | ||
globalThis.navigation = mockNavigation; | ||
|
||
beforeEach(() => { | ||
mockNavigation.navigate("/"); | ||
sessionStorage.clear(); | ||
}); | ||
|
||
test("`counter` can be updated.", async () => { | ||
// Arrange | ||
mockNavigation.navigate("/counter-update"); | ||
const { user } = renderWithUser(<LocationSyncCounterPage />); | ||
// Act | ||
await user.click(await screen.findByRole("button", { name: "increment" })); | ||
// Assert | ||
expect(screen.getByRole("heading")).toHaveTextContent("counter: 1"); | ||
}); | ||
|
||
test("`counter` is reset at navigation.", async () => { | ||
// Arrange | ||
mockNavigation.navigate("/counter-reset"); | ||
const { user } = renderWithUser(<LocationSyncCounterPage />); | ||
await user.click(await screen.findByRole("button", { name: "increment" })); | ||
// Act | ||
mockNavigation.navigate("/anywhere"); | ||
// Assert | ||
await waitFor(() => | ||
expect(screen.getByRole("heading")).toHaveTextContent("counter: 0"), | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { LocationStateContext } from "./context"; | ||
import { StorageStore } from "./stores/storage-store"; | ||
import { Store } from "./stores/types"; | ||
import { URLStore } from "./stores/url-store"; | ||
import { NavigationSyncer } from "./syncers/navigation-syncer"; | ||
|
||
import { Syncer } from "./types" | ||
import { ReactNode, useEffect, useState } from "react"; | ||
|
||
export function LocationStateProvider({ | ||
children, | ||
...props | ||
}: { | ||
syncer?: Syncer; | ||
children: ReactNode; | ||
}) { | ||
const [syncer] = useState( | ||
() => props.syncer ?? new NavigationSyncer(window.navigation), | ||
); | ||
const [contextValue] = useState(() => ({ | ||
stores: { | ||
session: new StorageStore(globalThis.sessionStorage), | ||
url: new URLStore("location-state", syncer), | ||
}, | ||
})); | ||
|
||
useEffect(() => { | ||
const stores = contextValue.stores; | ||
const abortController = new AbortController(); | ||
const { signal } = abortController; | ||
const applyAllStore = (callback: (store: Store) => void) => { | ||
Object.values(stores).forEach(callback); | ||
}; | ||
|
||
const key = syncer.key()!; | ||
applyAllStore((store) => store.load(key)); | ||
|
||
syncer.sync({ | ||
listener: (key) => { | ||
applyAllStore((store) => { | ||
store.save(); | ||
store.load(key); | ||
}); | ||
}, | ||
signal, | ||
}); | ||
window?.addEventListener( | ||
"beforeunload", | ||
() => { | ||
applyAllStore((store) => store.save()); | ||
}, | ||
{ signal }, | ||
); | ||
|
||
return () => abortController.abort(); | ||
}, [syncer, contextValue.stores]); | ||
|
||
return ( | ||
<LocationStateContext.Provider value={contextValue}> | ||
{children} | ||
</LocationStateContext.Provider> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './storage-store' | ||
export * from './url-store' | ||
export * from './types' |
Oops, something went wrong.