Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions t29-project-1/src/docs/FAVORITES_STORAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Favorite cats storage guide

This document explains how favorited cats are persisted in `localStorage`, so
favorites are restored after a page reload.

## Functions

The storage logic lives in `src/utils/favorites.ts` and exposes three
functions:

```ts
getFavoriteCats(): CatData[]
addFavoriteCat(cat: CatData): void
removeFavoriteCat(catId: string): void
```

`getFavoriteCats` reads the saved list of favorites, or returns an empty array
if none have been saved yet:

```ts
import { getFavoriteCats } from '../utils/favorites';

const favorites = getFavoriteCats();
```

`addFavoriteCat` saves a cat to the list:

```ts
import { addFavoriteCat } from '../utils/favorites';

addFavoriteCat(cat);
```

`removeFavoriteCat` removes a cat from the list by ID:

```ts
import { removeFavoriteCat } from '../utils/favorites';

removeFavoriteCat(cat.id);
```

## What is stored

Unlike the breed filter, which stores a single string, favorites are stored
as the full `CatData` object for each cat, not just its ID. This is because
there is currently no way to re-fetch a specific cat by ID from the API, so
the favorites page will need the stored `url` (and other fields) to display
each card directly from `localStorage`.

The list is stored under the key `favoriteCats`, serialized as JSON.

## Duplicate favorites

`addFavoriteCat` checks the existing list for a cat with the same `id` before
adding. Favoriting the same cat twice has no effect:

```ts
addFavoriteCat(cat); // added
addFavoriteCat(cat); // no-op, already in the list
```

## Error handling

All three functions wrap their `localStorage` access in a `try/catch`. If
storage is unavailable, for example in private browsing mode,
`getFavoriteCats` returns an empty array and the add/remove functions fail
silently instead of throwing. Callers do not need their own error handling
for this.

## Intended usage

No favorite button exists yet. Once one is built, it can call
`addFavoriteCat`/`removeFavoriteCat` when the user toggles a cat's favorite
status, and check whether a cat is already favorited with:

```ts
const isFavorite = getFavoriteCats().some((favorite) => favorite.id === cat.id);
```

## Testing

Tests for this module are in `src/tests/favorites.test.ts` and run with the
rest of the suite:

```bash
npm test
```

To run only this file, pass its path through to Vitest:

```bash
npm test -- src/tests/favorites.test.ts
```
42 changes: 42 additions & 0 deletions t29-project-1/src/tests/favorites.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { beforeEach, describe, expect, it } from 'vitest';
import {
addFavoriteCat,
getFavoriteCats,
removeFavoriteCat,
} from '../utils/favorites';

describe('favorite cats storage', () => {
beforeEach(() => {
localStorage.clear();
});

it('returns an empty list when no favorites have been saved', () => {
expect(getFavoriteCats()).toEqual([]);
});

it('adds a cat to favorites', () => {
addFavoriteCat({ id: 'cat-1', url: 'https://example.com/cat-1.jpg' });

expect(getFavoriteCats()).toEqual([
{ id: 'cat-1', url: 'https://example.com/cat-1.jpg' },
]);
});

it('does not add the same cat twice', () => {
addFavoriteCat({ id: 'cat-1', url: 'https://example.com/cat-1.jpg' });
addFavoriteCat({ id: 'cat-1', url: 'https://example.com/cat-1.jpg' });

expect(getFavoriteCats()).toHaveLength(1);
});

it('removes a cat from favorites', () => {
addFavoriteCat({ id: 'cat-1', url: 'https://example.com/cat-1.jpg' });
addFavoriteCat({ id: 'cat-2', url: 'https://example.com/cat-2.jpg' });

removeFavoriteCat('cat-1');

expect(getFavoriteCats()).toEqual([
{ id: 'cat-2', url: 'https://example.com/cat-2.jpg' },
]);
});
});
34 changes: 34 additions & 0 deletions t29-project-1/src/utils/favorites.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { CatData } from '../api/catApi';

const FAVORITE_CATS_STORAGE_KEY = 'favoriteCats';

export function getFavoriteCats(): CatData[] {
try {
const stored = localStorage.getItem(FAVORITE_CATS_STORAGE_KEY);
return stored ? (JSON.parse(stored) as CatData[]) : [];
} catch {
return [];
}
}

function saveFavoriteCats(cats: CatData[]): void {
try {
localStorage.setItem(FAVORITE_CATS_STORAGE_KEY, JSON.stringify(cats));
} catch {
// localStorage unavailable (e.g. private browsing) — favorite just won't persist
}
}

export function addFavoriteCat(cat: CatData): void {
const favorites = getFavoriteCats();

if (favorites.some((favorite) => favorite.id === cat.id)) return;

saveFavoriteCats([...favorites, cat]);
}

export function removeFavoriteCat(catId: string): void {
const favorites = getFavoriteCats();

saveFavoriteCats(favorites.filter((favorite) => favorite.id !== catId));
}