Skip to content

Commit

Permalink
refactor: use @contellar for state manipulation
Browse files Browse the repository at this point in the history
  • Loading branch information
prncss-xyz committed Sep 20, 2024
1 parent b9ff26c commit 36cc69e
Show file tree
Hide file tree
Showing 18 changed files with 372 additions and 340 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
},
"dependencies": {
"@babel/plugin-proposal-export-namespace-from": "^7.18.9",
"@constellar/core": "^2.0.3",
"@constellar/jotai": "^2.0.3",
"@constellar/core": "^2.0.5",
"@constellar/jotai": "^2.0.5",
"@expo-google-fonts/inter": "^0.2.3",
"@expo-google-fonts/roboto-mono": "^0.2.3",
"@expo/metro-runtime": "~3.2.3",
Expand Down
24 changes: 12 additions & 12 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 14 additions & 19 deletions src/components/clearList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,31 @@ import { createStore, Provider } from "jotai";
import { ClearList } from "./clearList";

import { timerListAtom } from "@/stores/timerLists";
import { TimerList } from "@/stores/timerLists/model";
import { mockLocalStorage } from "@/utils/localStorage";

jest.mock("@/utils/uuid", () => ({
getUUID: () => "x",
}));

describe("clearList", () => {
beforeEach(() => {
mockLocalStorage();
});
it("clears timerList", () => {
const timerList: TimerList = {
active: "a",
items: [
{ seconds: 1, id: "a" },
{ seconds: 2, id: "b" },
],
};
mockLocalStorage();
const store = createStore();
store.set(timerListAtom, timerList);
store.set(timerListAtom, { type: "clear", target: "a" });
store.set(timerListAtom, {
type: "setItemSeconds",
target: "a",
seconds: 1,
});
store.set(timerListAtom, {
type: "setItemSeconds",
target: "b",
seconds: 2,
});
render(
<Provider store={store}>
<ClearList />
</Provider>,
);
fireEvent.press(screen.getByText("clear all"));
expect(store.get(timerListAtom)).toEqual({
active: "x",
items: [{ seconds: 0, id: "x" }],
expect(store.get(timerListAtom)).toMatchObject({
items: [{ seconds: 0 }],
});
});
});
10 changes: 8 additions & 2 deletions src/components/clearList.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { useSetAtom } from "jotai";
import { useCallback } from "react";

import { WideButton } from "./wideButton";

import { useColor } from "@/hooks/color";
import { clearItemsAtom } from "@/stores/timerLists";
import { timerListAtom } from "@/stores/timerLists";
import { getUUID } from "@/utils/uuid";

export function ClearList() {
const clear = useSetAtom(clearItemsAtom);
const send = useSetAtom(timerListAtom);
const clear = useCallback(
() => send({ type: "clear", target: getUUID() }),
[send],
);
const danger = useColor("danger");
return <WideButton text="clear all" color={danger} onPress={clear} />;
}
60 changes: 35 additions & 25 deletions src/components/setTimer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,24 @@ import { createStore, Provider } from "jotai";
import { SetTimer } from "./setTimer";

import { timerListAtom } from "@/stores/timerLists";
import { TimerList } from "@/stores/timerLists/model";
import { mockLocalStorage } from "@/utils/localStorage";

jest.mock("expo-router", () => ({
router: { back: jest.fn() },
}));

describe("set-timer", () => {
beforeEach(() => {
mockLocalStorage();
});
it("append digits to timer", () => {
const timerList: TimerList = {
active: "a",
items: [{ seconds: 1, id: "a" }],
};
mockLocalStorage();
for (let i = 0; i < 10; ++i) {
const digit = String(i);
const store = createStore();
store.set(timerListAtom, timerList);
store.set(timerListAtom, { type: "clear", target: "a" });
store.set(timerListAtom, {
type: "setItemSeconds",
target: "a",
seconds: 1,
});
render(
<Provider store={store}>
<SetTimer timerId="a" />
Expand All @@ -41,11 +39,13 @@ describe("set-timer", () => {
}
});
it("appends 00 to timer", () => {
mockLocalStorage();
const store = createStore();
store.set(timerListAtom, { type: "clear", target: "a" });
store.set(timerListAtom, {
active: "a",
// 23 * 3600 + 1 => "23:00:01"
items: [{ seconds: 23 * 3600 + 1, id: "a" }],
type: "setItemSeconds",
target: "a",
seconds: 1,
});
render(
<Provider store={store}>
Expand All @@ -56,11 +56,13 @@ describe("set-timer", () => {
within(screen.getByLabelText("timer")).getByText("00:01:00");
});
it("closes without updating value", () => {
jest.resetAllMocks();
mockLocalStorage();
const store = createStore();
store.set(timerListAtom, { type: "clear", target: "a" });
store.set(timerListAtom, {
active: "a",
items: [{ seconds: 1, id: "a" }],
type: "setItemSeconds",
target: "a",
seconds: 1,
});
render(
<Provider store={store}>
Expand All @@ -69,15 +71,19 @@ describe("set-timer", () => {
);
fireEvent.press(screen.getByText("1"));
fireEvent.press(screen.getByLabelText("cancel"));
expect(store.get(timerListAtom).items).toEqual([{ seconds: 1, id: "a" }]);
expect((router.back as any).mock.calls).toHaveLength(1);
expect(store.get(timerListAtom).items).toMatchObject([
{ seconds: 1, id: "a" },
]);
expect(router.back).toHaveBeenCalled();
});
it("closes and updates value", () => {
jest.resetAllMocks();
mockLocalStorage();
const store = createStore();
store.set(timerListAtom, { type: "clear", target: "a" });
store.set(timerListAtom, {
active: "a",
items: [{ seconds: 1, id: "a" }],
type: "setItemSeconds",
target: "a",
seconds: 1,
});
render(
<Provider store={store}>
Expand All @@ -86,15 +92,19 @@ describe("set-timer", () => {
);
fireEvent.press(screen.getByText("1"));
fireEvent.press(screen.getByLabelText("done"));
expect(store.get(timerListAtom).items).toEqual([{ seconds: 11, id: "a" }]);
expect((router.back as any).mock.calls).toHaveLength(1);
expect(store.get(timerListAtom).items).toMatchObject([
{ seconds: 11, id: "a" },
]);
expect(router.back).toHaveBeenCalled();
});
it("removes last character to timer", () => {
mockLocalStorage();
const store = createStore();
store.set(timerListAtom, { type: "clear", target: "a" });
store.set(timerListAtom, {
active: "a",
// 23 * 3600 + 1 => "12:00:34"
items: [{ seconds: 12 * 3600 + 34, id: "a" }],
type: "setItemSeconds",
target: "a",
seconds: 12 * 3600 + 34,
});
render(
<Provider store={store}>
Expand Down
12 changes: 7 additions & 5 deletions src/components/setTimer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import { WideButton } from "./wideButton";
import { HeadSeparator } from "@/components/headSeparator";
import { TimerView } from "@/components/timerView";
import { useColor } from "@/hooks/color";
import { getIdItemSecondsTextAtom } from "@/stores/timerLists";
import { getItemSecondsAtom } from "@/stores/timerLists";
import { borderWidths, fontSizes, sizes, spaces, styles } from "@/styles";
import { normalizeSeconds } from "@/utils/seconds";
import { normalizeSeconds, secondsString } from "@/utils/seconds";

const rawTextAtom = atom("");
const textAtom = focusAtom(rawTextAtom, rewrite(normalizeSeconds));
Expand Down Expand Up @@ -159,10 +159,12 @@ function Grid() {
}

export function SetTimer({ timerId }: { timerId: string }) {
const [text, setText] = useAtom(
useMemo(() => getIdItemSecondsTextAtom(timerId), [timerId]),
const itemSecondsAtom = useMemo(() => getItemSecondsAtom(timerId), [timerId]);
const textAtom = useMemo(
() => focusAtom(itemSecondsAtom, secondsString),
[itemSecondsAtom],
);
if (text === undefined) return null;
const [text, setText] = useAtom(textAtom);
return (
<Provider>
{/* we can only pass serializable values, hence setText is not passed as an atom here, but as a prop later on */}
Expand Down
54 changes: 30 additions & 24 deletions src/components/timerBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,54 +14,55 @@ import { timerListAtom } from "@/stores/timerLists";
import { mockLocalStorage } from "@/utils/localStorage";

describe("timerBar", () => {
beforeEach(() => {
mockLocalStorage();
});
it("decreases count when playing, and keeps count when not playing (plause-play button)", () => {
mockLocalStorage();
const store = createStore();
store.set(timerListAtom, { type: "clear", target: "a" });
store.set(timerListAtom, {
active: "a",
items: [
{ seconds: 2, id: "a" },
{ seconds: 3, id: "b" },
],
type: "setItemSeconds",
target: "a",
seconds: 3,
});
render(
<Provider store={store}>
<TimerBar />
</Provider>,
);
within(screen.getByLabelText("countdown")).getByText("00:00:03");
// start playing
fireEvent.press(screen.getByLabelText("play"));
act(() => {
store.set(nowAtom, (now) => now + 1000);
});
within(screen.getByLabelText("countdown")).getByText("00:00:01");
// stop playing
within(screen.getByLabelText("countdown")).getByText("00:00:02");
fireEvent.press(screen.getByLabelText("pause"));
act(() => {
store.set(nowAtom, (now) => now + 1000);
});
within(screen.getByLabelText("countdown")).getByText("00:00:01");
fireEvent.press(screen.getByLabelText("play"));
act(() => {
store.set(nowAtom, (now) => now + 1000);
});
within(screen.getByLabelText("countdown")).getByText("00:00:02");
});
it("decreases count when playing, and keep count when not playing (countdown button)", () => {
mockLocalStorage();
const store = createStore();
store.set(timerListAtom, { type: "clear", target: "a" });
store.set(timerListAtom, {
active: "a",
items: [
{ seconds: 2, id: "a" },
{ seconds: 3, id: "b" },
],
type: "setItemSeconds",
target: "a",
seconds: 2,
});
store.set(timerListAtom, {
type: "setItemSeconds",
target: "b",
seconds: 3,
});
render(
<Provider store={store}>
<TimerBar />
</Provider>,
);
within(screen.getByLabelText("countdown")).getByText("00:00:02");

// start playing
fireEvent.press(screen.getByLabelText("countdown"));
act(() => {
Expand All @@ -76,13 +77,18 @@ describe("timerBar", () => {
within(screen.getByLabelText("countdown")).getByText("00:00:01");
});
it("resets timer", () => {
mockLocalStorage();
const store = createStore();
store.set(timerListAtom, { type: "clear", target: "a" });
store.set(timerListAtom, {
type: "setItemSeconds",
target: "a",
seconds: 2,
});
store.set(timerListAtom, {
active: "a",
items: [
{ seconds: 2, id: "a" },
{ seconds: 3, id: "b" },
],
type: "setItemSeconds",
target: "b",
seconds: 3,
});
render(
<Provider store={store}>
Expand Down
Loading

0 comments on commit 36cc69e

Please sign in to comment.