Skip to content

Commit

Permalink
add more unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
vorant94 committed Jun 20, 2024
1 parent 6e645e7 commit d2d809f
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 30 deletions.
10 changes: 10 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react": "^7.34.2",
"eslint-plugin-react-hooks": "^4.6.2",
"fake-indexeddb": "^6.0.0",
"happy-dom": "^14.12.0",
"husky": "^9.0.11",
"postcss": "^8.4.38",
Expand Down
31 changes: 15 additions & 16 deletions src/subscriptions/hooks/use-subscription-upsert.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ import {
} from './use-subscription-upsert.tsx';

describe('useSubscriptionUpsert', () => {
let renderResult: RenderHookResult<HooksCombined, void>;
let hooks: RenderHookResult<HooksCombined, void>['result'];

beforeEach(() => {
const renderResult = renderHook<HooksCombined, void>(
renderResult = renderHook<HooksCombined, void>(
() => ({
upsert: useSubscriptionUpsert(),
defaultLayout: useDefaultLayout(),
Expand All @@ -32,33 +33,31 @@ describe('useSubscriptionUpsert', () => {

it('should open/close drawer on upsert open/close', () => {
expect(hooks.current.defaultLayout.isDrawerOpened).toBeFalsy();
expect(hooks.current.upsert.state.mode).toBeFalsy();

act(() => {
hooks.current.upsert.dispatch({ type: 'open' });
});

act(() => hooks.current.upsert.dispatch({ type: 'open' }));
expect(hooks.current.defaultLayout.isDrawerOpened).toBeTruthy();

act(() => {
hooks.current.upsert.dispatch({ type: 'close' });
});

act(() => hooks.current.upsert.dispatch({ type: 'close' }));
expect(hooks.current.defaultLayout.isDrawerOpened).toBeFalsy();
});

it('should close upsert on drawer close', () => {
expect(hooks.current.defaultLayout.isDrawerOpened).toBeFalsy();
expect(hooks.current.upsert.state.mode).toBeFalsy();

act(() => {
hooks.current.upsert.dispatch({ type: 'open' });
});

act(() => hooks.current.upsert.dispatch({ type: 'open' }));
expect(hooks.current.defaultLayout.isDrawerOpened).toBeTruthy();

act(() => {
hooks.current.defaultLayout.drawer.close();
});
act(() => hooks.current.defaultLayout.drawer.close());
expect(hooks.current.upsert.state.mode).toBeFalsy();
});

it(`shouldn't open upsert on drawer open`, () => {
expect(hooks.current.defaultLayout.isDrawerOpened).toBeFalsy();
expect(hooks.current.upsert.state.mode).toBeFalsy();

act(() => hooks.current.defaultLayout.drawer.open());
expect(hooks.current.upsert.state.mode).toBeFalsy();
});
});
Expand Down
140 changes: 140 additions & 0 deletions src/subscriptions/hooks/use-subscriptions.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { db } from '@/db/globals/db.ts';
import { tag } from '@/tags/models/tag.mock.ts';
import {
act,
renderHook,
waitFor,
type RenderHookResult,
} from '@testing-library/react';
import type { FC, PropsWithChildren } from 'react';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import {
SubscriptionsProvider,
useSubscriptions,
type UseSubscriptions,
} from '../hooks/use-subscriptions.tsx';
import { subscriptions } from '../models/subscription.mock.ts';

describe('useSubscriptions', () => {
let renderResult: RenderHookResult<UseSubscriptions, void>;
let hook: RenderHookResult<UseSubscriptions, void>['result'];

beforeEach(() => {
renderResult = renderHook<UseSubscriptions, void>(
() => useSubscriptions(),
{
wrapper,
},
);

hook = renderResult.result;
});

beforeEach(async () => await populateDb());

afterEach(async () => await cleanUpDb());

it('should fetch subscriptions and tags', async () => {
await Promise.all([
waitFor(() =>
expect(
hook.current.tags.length,
'have initially one populated tag',
).toEqual(1),
),
waitFor(() =>
expect(
hook.current.subscriptions.length,
'have initially two populated subscriptions',
).toEqual(2),
),
]);
});

it('should filter out subscriptions based on selected tag', async () => {
await Promise.all([
waitFor(() =>
expect(
hook.current.tags.length,
'have initially one populated tag',
).toEqual(1),
),
waitFor(() =>
expect(
hook.current.subscriptions.length,
'have initially two populated subscriptions',
).toEqual(2),
),
]);

act(() => hook.current.selectTag(`${tag.id}`));

await Promise.all([
waitFor(() =>
expect(
hook.current.selectedTag?.id,
'selected tag to be there',
).toEqual(tag.id),
),
waitFor(() =>
expect(
hook.current.subscriptions.length,
'decrease amount of filtered subscriptions',
).toEqual(1),
),
]);

for (const subscription of hook.current.subscriptions) {
expect(
subscription.tags.find(
(tag) => tag.id === hook.current.selectedTag?.id,
),
'all left subscriptions should have selected tag in it',
).toBeTruthy();
}
});
});

const wrapper: FC<PropsWithChildren> = ({ children }) => {
return <SubscriptionsProvider>{children}</SubscriptionsProvider>;
};

async function populateDb(): Promise<void> {
await db.transaction(
`rw`,
db.subscriptionsTags,
db.subscriptions,
db.tags,
async () => {
for (const { tags, ...subscription } of subscriptions) {
const tagPuts = tags.map((tag) => db.tags.put(tag));
const tagLinkPuts = tags.map((tag) =>
db.subscriptionsTags.put({
tagId: tag.id,
subscriptionId: subscription.id,
}),
);

await Promise.all([
...tagPuts,
db.subscriptions.put(subscription),
...tagLinkPuts,
]);
}
},
);
}

async function cleanUpDb(): Promise<void> {
await db.transaction(
'rw',
db.subscriptionsTags,
db.subscriptions,
db.tags,
async () => {
await db.subscriptionsTags.clear();
await db.subscriptions.clear();
await db.tags.clear();
},
);
}
14 changes: 8 additions & 6 deletions src/subscriptions/hooks/use-subscriptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,18 @@ export interface UseSubscriptions {
}

export const SubscriptionsProvider = memo(({ children }: PropsWithChildren) => {
const unfilteredSubscriptions = useLiveQuery(() => findSubscriptions());
const tags = useLiveQuery(() => findTags());
const unfilteredSubscriptions = useLiveQuery(
() => findSubscriptions(),
[],
[],
);
const tags = useLiveQuery(() => findTags(), [], []);
const [selectedTag, setSelectedTag] = useState<TagModel | null>(null);

const selectTag: (tagId: string | null) => void = useCallback(
(tagId) => {
if (tagId) {
setSelectedTag(
(tags ?? []).find((tag) => `${tag.id}` === tagId) ?? null,
);
setSelectedTag(tags.find((tag) => `${tag.id}` === tagId) ?? null);
} else {
setSelectedTag(null);
}
Expand All @@ -44,7 +46,7 @@ export const SubscriptionsProvider = memo(({ children }: PropsWithChildren) => {

const subscriptions = useMemo(
() =>
(unfilteredSubscriptions ?? []).filter(
unfilteredSubscriptions.filter(
(subscription) =>
!selectedTag ||
subscription.tags.find((subTag) => subTag.id === selectedTag.id),
Expand Down
12 changes: 4 additions & 8 deletions src/subscriptions/models/subscription.mock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { findSubscriptions } from '@/subscriptions/models/subscription.table.ts';
import { tag } from '@/tags/models/tag.mock.ts';
import dayjs from 'dayjs';
import type { SubscriptionModel } from './subscription.model.ts';

Expand All @@ -12,11 +12,11 @@ export const monthlySubscription = {
each: 1,
period: 'monthly',
},
tags: [],
tags: [tag],
} as const satisfies SubscriptionModel;

export const yearlySubscription = {
id: 1,
id: 2,
name: 'Netflix',
price: 13.33,
startedAt: dayjs(new Date()).set('month', 2).toDate(),
Expand All @@ -28,8 +28,4 @@ export const yearlySubscription = {
tags: [],
} as const satisfies SubscriptionModel;

export async function findSubscriptionsMock(): ReturnType<
typeof findSubscriptions
> {
return [monthlySubscription, yearlySubscription];
}
export const subscriptions = [monthlySubscription, yearlySubscription] as const;
1 change: 1 addition & 0 deletions src/subscriptions/models/subscription.table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export function findSubscriptions(): Promise<Array<SubscriptionModel>> {
);
}

// TODO optionally allow to accept transaction
export function getSubscription(id: number): Promise<SubscriptionModel> {
return db.transaction(
'r',
Expand Down
7 changes: 7 additions & 0 deletions src/tags/models/tag.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { TagModel } from './tag.model.ts';

export const tag = {
id: 1,
name: 'Basics',
color: '#000000',
} as const satisfies TagModel;
1 change: 1 addition & 0 deletions test-setup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import '@testing-library/jest-dom/vitest';
import { cleanup } from '@testing-library/react';
import 'fake-indexeddb/auto';
import { afterEach } from 'vitest';

afterEach(() => {
Expand Down

0 comments on commit d2d809f

Please sign in to comment.