Skip to content

Commit

Permalink
refactor: initialize localforage instances in respective repos
Browse files Browse the repository at this point in the history
Signed-off-by: rare-magma <[email protected]>
  • Loading branch information
rare-magma committed Sep 29, 2024
1 parent f1abc8a commit 36fdb9c
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 57 deletions.
22 changes: 9 additions & 13 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { act, cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";
import { App } from "./App";
import { budgetsDB, calcHistDB, optionsDB } from "./db";
import { BudgetMother } from "./guitos/domain/budget.mother";
import { budgetContextSpy, testEmptyBudgetContext } from "./setupTests";
import { localForageBudgetRepository } from "./guitos/infrastructure/localForageBudgetRepository";

const budgetRepository = new localForageBudgetRepository();

describe("App", () => {
const comp = <App />;
Expand All @@ -19,15 +21,9 @@ describe("App", () => {
"Figure out where your money went, plan ahead of time and analyze past expenditures.",
),
).toBeInTheDocument();
expect(budgetsDB.config("name")).toBe("guitos");
expect(budgetsDB.config("storeName")).toBe("budgets");
expect(optionsDB.config("name")).toBe("guitos");
expect(optionsDB.config("storeName")).toBe("options");
expect(calcHistDB.config("name")).toBe("guitos");
expect(calcHistDB.config("storeName")).toBe("calcHistDB");
await expect(
budgetsDB.getItem(BudgetMother.testBudget().id.toString()),
).resolves.toBeNull();
budgetRepository.get(BudgetMother.testBudget().id),
).rejects.toThrow();
});

it("shows new budget when clicking new button", async () => {
Expand All @@ -42,15 +38,15 @@ describe("App", () => {
expect(await screen.findByText("Revenue")).toBeInTheDocument();
expect(await screen.findByText("Expenses")).toBeInTheDocument();
await expect(
budgetsDB.getItem(BudgetMother.testBudget().id.toString()),
budgetRepository.get(BudgetMother.testBudget().id),
).resolves.toEqual(BudgetMother.testBudget());
});

it("deletes budget when clicking delete button", async () => {
render(comp);
await act(async () => {
await expect(
budgetsDB.getItem(BudgetMother.testBudget().id.toString()),
budgetRepository.get(BudgetMother.testBudget().id),
).resolves.toEqual(BudgetMother.testBudget());
});

Expand All @@ -70,8 +66,8 @@ describe("App", () => {

await act(async () => {
await expect(
budgetsDB.getItem(BudgetMother.testBudget().id.toString()),
).resolves.toBeNull();
budgetRepository.get(BudgetMother.testBudget().id),
).rejects.toThrow();
});
});
});
16 changes: 0 additions & 16 deletions src/db.ts

This file was deleted.

64 changes: 64 additions & 0 deletions src/guitos/context/ConfigContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {
type PropsWithChildren,
createContext,
useContext,
useState,
} from "react";
import type {
CurrencyInputProps,
IntlConfig,
} from "react-currency-input-field/dist/components/CurrencyInputProps";
import { localForageOptionsRepository } from "../infrastructure/localForageOptionsRepository";

interface ConfigContextInterface {
intlConfig: IntlConfig | undefined;
setIntlConfig: (value: IntlConfig) => void;
currency: string;
setCurrency: (value: string) => void;
}

const optionsRepository = new localForageOptionsRepository();

const ConfigContext = createContext<ConfigContextInterface>({
intlConfig: {
locale: optionsRepository.getUserLang(),
currency: optionsRepository.getDefaultCurrencyCode(),
},
setIntlConfig: (value: IntlConfig) => value,
currency: optionsRepository.getDefaultCurrencyCode(),
setCurrency: (value: string) => value,
});

function useConfig() {
const { currency, setCurrency, intlConfig, setIntlConfig } =
useContext(ConfigContext);

function handleCurrency(c: string) {
setCurrency(c);
setIntlConfig({ locale: optionsRepository.getUserLang(), currency: c });
}

return { intlConfig, setIntlConfig, currency, handleCurrency };
}

function ConfigProvider({ children }: PropsWithChildren) {
const [currency, setCurrency] = useState<string>(
optionsRepository.getDefaultCurrencyCode(),
);
const [intlConfig, setIntlConfig] = useState<
CurrencyInputProps["intlConfig"]
>({
locale: optionsRepository.getUserLang(),
currency: currency,
});

return (
<ConfigContext.Provider
value={{ intlConfig, setIntlConfig, currency, setCurrency }}
>
{children}
</ConfigContext.Provider>
);
}

export { ConfigProvider, useConfig };
24 changes: 18 additions & 6 deletions src/guitos/infrastructure/localForageBudgetRepository.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import localforage from "localforage";
import { Budget } from "../domain/budget";
import type { BudgetRepository } from "../domain/budgetRepository";
import type { Uuid } from "../domain/uuid";
import { budgetsDB } from "./localForageDb";

export class localForageBudgetRepository implements BudgetRepository {
private readonly budgetsDB;

constructor() {
this.budgetsDB = localforage.createInstance({
name: "guitos",
storeName: "budgets",
});
}

async get(id: Uuid): Promise<Budget> {
try {
const budget = await budgetsDB.getItem<Budget>(id.toString());
const budget = await this.budgetsDB.getItem<Budget>(id.toString());
if (!budget) throw new Error();
return budget;
} catch (e) {
Expand All @@ -17,9 +26,9 @@ export class localForageBudgetRepository implements BudgetRepository {
async getAll(): Promise<Budget[]> {
try {
const list: Budget[] = [];
for (const item of await budgetsDB.keys()) {
for (const item of await this.budgetsDB.keys()) {
if (item) {
const budget = await budgetsDB.getItem<Budget>(item);
const budget = await this.budgetsDB.getItem<Budget>(item);
if (budget) {
list.push(budget);
}
Expand All @@ -33,7 +42,10 @@ export class localForageBudgetRepository implements BudgetRepository {

async update(id: Uuid, newBudget: Budget): Promise<boolean> {
try {
await budgetsDB.setItem(id.toString(), Budget.toSafeFormat(newBudget));
await this.budgetsDB.setItem(
id.toString(),
Budget.toSafeFormat(newBudget),
);
return true;
} catch {
return false;
Expand All @@ -42,7 +54,7 @@ export class localForageBudgetRepository implements BudgetRepository {

async delete(id: Uuid): Promise<boolean> {
try {
await budgetsDB.removeItem(id.toString());
await this.budgetsDB.removeItem(id.toString());
return true;
} catch {
return false;
Expand Down
20 changes: 14 additions & 6 deletions src/guitos/infrastructure/localForageCalcHistRepository.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import localforage from "localforage";
import type { CalcHistRepository } from "../domain/calcHistRepository";
import type { CalculationHistoryItem } from "../domain/calculationHistoryItem";
import { calcHistDB } from "./localForageDb";

export class localForageCalcHistRepository implements CalcHistRepository {
private readonly calcHistDB;

constructor() {
this.calcHistDB = localforage.createInstance({
name: "guitos",
storeName: "calcHistDB",
});
}
async get(id: string): Promise<CalculationHistoryItem[] | null> {
try {
return await calcHistDB.getItem<CalculationHistoryItem[]>(id);
return await this.calcHistDB.getItem<CalculationHistoryItem[]>(id);
} catch {
return null;
}
Expand All @@ -14,10 +22,10 @@ export class localForageCalcHistRepository implements CalcHistRepository {
async getAll(): Promise<CalculationHistoryItem[][] | null> {
try {
const list: CalculationHistoryItem[][] = [];
for (const item of await calcHistDB.keys()) {
for (const item of await this.calcHistDB.keys()) {
if (item) {
const calcHist =
await calcHistDB.getItem<CalculationHistoryItem[]>(item);
await this.calcHistDB.getItem<CalculationHistoryItem[]>(item);
if (calcHist) {
list.push(calcHist);
}
Expand All @@ -34,7 +42,7 @@ export class localForageCalcHistRepository implements CalcHistRepository {
newCalcHist: CalculationHistoryItem[],
): Promise<boolean> {
try {
await calcHistDB.setItem(
await this.calcHistDB.setItem(
id,
newCalcHist.map((item) => item),
);
Expand All @@ -46,7 +54,7 @@ export class localForageCalcHistRepository implements CalcHistRepository {

async delete(id: string): Promise<boolean> {
try {
await calcHistDB.removeItem(id);
await this.calcHistDB.removeItem(id);
return true;
} catch {
return false;
Expand Down
16 changes: 0 additions & 16 deletions src/guitos/infrastructure/localForageDb.ts

This file was deleted.

120 changes: 120 additions & 0 deletions src/guitos/sections/Budget/BudgetPage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { act } from "react-dom/test-utils";
import { BrowserRouter } from "react-router-dom";
import { describe, expect, it } from "vitest";
import { BudgetMother } from "../../domain/budget.mother";
import {
budgetContextSpy,
redoMock,
setBudgetMock,
setNotificationsMock,
testBudgetContext,
undoMock,
} from "../../../setupTests";
import { BudgetPage } from "./BudgetPage";
import { localForageBudgetRepository } from "../../infrastructure/localForageBudgetRepository";

const budgetRepository = new localForageBudgetRepository();

describe("BudgetPage", () => {
const comp = (
<BrowserRouter>
<BudgetPage />
</BrowserRouter>
);

it("matches snapshot", () => {
render(comp);
expect(comp).toMatchSnapshot();
});

it("renders initial state", async () => {
render(comp);
const newButton = screen.getAllByRole("button", { name: "new budget" });
await act(async () => {
await userEvent.click(newButton[0]);
});
expect(screen.getByLabelText("delete budget")).toBeInTheDocument();
});

it("responds to new budget keyboard shortcut", async () => {
render(comp);
await userEvent.type(await screen.findByTestId("header"), "a");
expect(setBudgetMock).toHaveBeenCalled();
});

it("removes budget when clicking on delete budget button", async () => {
render(comp);
const deleteButton = await screen.findAllByRole("button", {
name: "delete budget",
});
await userEvent.click(deleteButton[0]);
await userEvent.click(
await screen.findByRole("button", { name: "confirm budget deletion" }),
);
await expect(
budgetRepository.get(BudgetMother.testBudget().id),
).rejects.toThrow();
});

it.skip("clones budget when clicking on clone budget button", async () => {
render(comp);
const newButton = await screen.findAllByRole("button", {
name: "new budget",
});
await userEvent.click(newButton[0]);

const cloneButton = screen.getAllByRole("button", {
name: "clone budget",
});
await userEvent.click(cloneButton[0]);
expect(setBudgetMock).toHaveBeenCalledWith(
BudgetMother.testBudgetClone(),
true,
);
});

it.skip("responds to clone budget keyboard shortcut", async () => {
render(comp);
const newButton = await screen.findAllByRole("button", {
name: "new budget",
});
await userEvent.click(newButton[0]);

await userEvent.type(await screen.findByTestId("header"), "c");
expect(setBudgetMock).toHaveBeenCalledWith(
BudgetMother.testBudgetClone(),
true,
);
});

it("responds to undo change keyboard shortcut", async () => {
cleanup();
budgetContextSpy.mockReturnValue({ ...testBudgetContext, canUndo: true });
render(comp);
await userEvent.type(await screen.findByTestId("header"), "u");
expect(undoMock).toHaveBeenCalled();
});

it("responds to redo change keyboard shortcut", async () => {
cleanup();
budgetContextSpy.mockReturnValue({ ...testBudgetContext, canRedo: true });
render(comp);
await userEvent.type(await screen.findByTestId("header"), "r");
expect(redoMock).toHaveBeenCalled();
});

it("responds to clear notifications keyboard shortcut", async () => {
render(comp);
setNotificationsMock.mockClear();
await userEvent.type(await screen.findByTestId("header"), "{Escape}");
expect(setNotificationsMock).toHaveBeenCalledWith([]);
});

it("responds to show graphs keyboard shortcut", async () => {
render(comp);
await userEvent.type(await screen.findByTestId("header"), "i");
expect(screen.getByRole("status")).toBeInTheDocument();
});
});

0 comments on commit 36fdb9c

Please sign in to comment.