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
78 changes: 78 additions & 0 deletions t29-project-1/src/docs/FILTER_STORAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Breed filter storage guide

This document explains how the selected breed filter is persisted in
`localStorage`, so the choice is restored after a page reload.

## Functions

The storage logic lives in `src/utils/breedFilter.ts` and exposes two
functions:

```ts
getStoredBreedFilter(): string | null
setStoredBreedFilter(breedId: string | null): void
```

`getStoredBreedFilter` reads the saved breed ID, or returns `null` if none has
been saved yet:

```ts
import { getStoredBreedFilter } from '../utils/breedFilter';

const breedId = getStoredBreedFilter();
```

`setStoredBreedFilter` saves a breed ID:

```ts
import { setStoredBreedFilter } from '../utils/breedFilter';

setStoredBreedFilter('beng');
```

## Storage key

The value is stored under the key `selectedBreedId`. Passing `null` (or an
empty string) to `setStoredBreedFilter` clears the filter.

```ts
setStoredBreedFilter(null);
```

Clearing removes the key from `localStorage` entirely, instead of storing an
empty string. This has two purposes:

- **No leftover state.** Once a filter is cleared, nothing related to it stays
in storage.
- **One consistent "not set" value.** `getStoredBreedFilter` always returns
`null` when there is no filter, never an empty string. Callers only have to
check for one value instead of two that mean the same thing.

## Error handling

Both functions wrap their `localStorage` access in a `try/catch`. If storage
is unavailable, for example in private browsing mode, the functions fail
silently instead of throwing. `getStoredBreedFilter` returns `null` and
`setStoredBreedFilter` simply does not persist the value. Callers do not need
their own error handling for this.

## Intended usage

No breed dropdown exists yet, but once one is built it should read the saved
value as its initial state and call `setStoredBreedFilter` whenever the
selection changes.

## Testing

Tests for this module are in `src/tests/breedFilter.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/breedFilter.test.ts
```
35 changes: 35 additions & 0 deletions t29-project-1/src/tests/breedFilter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { beforeEach, describe, expect, it } from 'vitest';
import {
getStoredBreedFilter,
setStoredBreedFilter,
} from '../utils/breedFilter';

describe('breed filter storage', () => {
beforeEach(() => {
localStorage.clear();
});

it('returns null when no breed filter has been saved', () => {
expect(getStoredBreedFilter()).toBeNull();
});

it('saves and retrieves the selected breed filter', () => {
setStoredBreedFilter('abys');

expect(getStoredBreedFilter()).toBe('abys');
});

it('overwrites a previously saved breed filter', () => {
setStoredBreedFilter('abys');
setStoredBreedFilter('beng');

expect(getStoredBreedFilter()).toBe('beng');
});

it('clears the stored filter when set to null', () => {
setStoredBreedFilter('abys');
setStoredBreedFilter(null);

expect(getStoredBreedFilter()).toBeNull();
});
});
21 changes: 21 additions & 0 deletions t29-project-1/src/utils/breedFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const BREED_FILTER_STORAGE_KEY = 'selectedBreedId';

export function getStoredBreedFilter(): string | null {
try {
return localStorage.getItem(BREED_FILTER_STORAGE_KEY);
} catch {
return null;
}
}

export function setStoredBreedFilter(breedId: string | null): void {
try {
if (breedId) {
localStorage.setItem(BREED_FILTER_STORAGE_KEY, breedId);
} else {
localStorage.removeItem(BREED_FILTER_STORAGE_KEY);
}
} catch {
// localStorage unavailable (e.g. private browsing) — filter just won't persist
}
}