Skip to content

Commit

Permalink
Merge pull request #11 from sophiabrandt/class-access-component-names
Browse files Browse the repository at this point in the history
Rename files, encapsulate Store methods better
  • Loading branch information
sophiabrandt authored Jun 30, 2024
2 parents 790d27d + 3b8e77a commit 8b68233
Show file tree
Hide file tree
Showing 13 changed files with 109 additions and 105 deletions.
4 changes: 2 additions & 2 deletions src/restaurants/RestaurantScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useRestaurantStore } from './hooks/useRestaurantStore';
import { observer } from 'mobx-react-lite';
import { Card, CardContent, Typography } from '@mui/material';
import { NewRestaurantForm } from './ui/NewRestaurantForm';
import { RestaurantDisplay } from './ui/RestaurantDisplay';
import { RestaurantView } from './ui/RestaurantView';
import { RestaurantSavingError } from './ui/RestaurantSavingError';

export const RestaurantScreen = observer(() => {
Expand All @@ -16,7 +16,7 @@ export const RestaurantScreen = observer(() => {
</Typography>
<RestaurantSavingError store={store} />
<NewRestaurantForm store={store} />
<RestaurantDisplay store={store} />
<RestaurantView store={store} />
</CardContent>
</Card>
);
Expand Down
3 changes: 1 addition & 2 deletions src/restaurants/store/IRestaurantStore.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Resource } from '@/utils/create-resource';
import { Restaurant } from '../types/Restaurant';
import { ITransportLayer } from './ITransportLayer';
import { CancellablePromise } from 'mobx/dist/internal';

export interface IRestaurantStore {
restaurantsResource: Resource<Restaurant[]>;
createRestaurant: (
restaurant: Partial<Restaurant>
) => CancellablePromise<Restaurant | undefined>;
read: () => Restaurant[];
getRestaurants: () => CancellablePromise<void>;
transportLayer: ITransportLayer<Restaurant>;
isSaving: boolean;
Expand Down
42 changes: 30 additions & 12 deletions src/restaurants/store/RestaurantStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@ import { flow, makeAutoObservable } from 'mobx';
import { Restaurant } from '../types/Restaurant';
import { ITransportLayer } from './ITransportLayer';
import { IRestaurantStore } from './IRestaurantStore';
import { Resource, createResource } from '@/utils/create-resource';
import { createResource } from '@/utils/create-resource';

export class RestaurantStore implements IRestaurantStore {
restaurantsResource = createResource() as Resource<Restaurant[]>;
readonly transportLayer: ITransportLayer<Restaurant>;
isSaving = false;
isSavingError = false;
private readonly restaurantsResource = createResource<Restaurant[]>();
private _isSaving = false;
private _isSavingError = false;

constructor(transportLayer: ITransportLayer<Restaurant>) {
constructor(readonly transportLayer: ITransportLayer<Restaurant>) {
makeAutoObservable(this, {}, { autoBind: true });
this.transportLayer = transportLayer;

this.getRestaurants();
}
Expand All @@ -21,8 +19,8 @@ export class RestaurantStore implements IRestaurantStore {
this: RestaurantStore,
restaurant: Partial<Restaurant>
) {
this.isSaving = true;
this.isSavingError = false;
this.setIsSaving(true);
this.setIsSavingError(false);
try {
const created: Restaurant | undefined =
yield this.transportLayer.create(restaurant);
Expand All @@ -32,11 +30,11 @@ export class RestaurantStore implements IRestaurantStore {
created,
]);
}
this.isSaving = false;
this.setIsSaving(false);
return created;
} catch (error) {
this.isSaving = false;
this.isSavingError = true;
this.setIsSaving(false);
this.setIsSavingError(true);
/* v8 ignore start */
console.error(
`Error saving restaurant: ${error instanceof Error ? error.message : error}`
Expand All @@ -51,4 +49,24 @@ export class RestaurantStore implements IRestaurantStore {
);
return restaurants;
});

read = () => {
return this.restaurantsResource.read();
};

get isSaving() {
return this._isSaving;
}

get isSavingError() {
return this._isSavingError;
}

private setIsSaving(value: boolean) {
this._isSaving = value;
}

private setIsSavingError(value: boolean) {
this._isSavingError = value;
}
}
9 changes: 7 additions & 2 deletions src/restaurants/store/RestaurantTransportLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ export class RestaurantTransportLayer implements ITransportLayer<Restaurant> {
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(
`Failed to create restaurant: ${response.status} ${response.statusText}`
);
}
return await response.json();
} catch (error) {
throw new Error(
`Failed to create restaurant: ${error instanceof Error && error.message}`
`Failed to create restaurant: ${error instanceof Error ? error.message : error}`
);
}
}
Expand All @@ -33,7 +38,7 @@ export class RestaurantTransportLayer implements ITransportLayer<Restaurant> {
return await response.json();
} catch (error) {
throw new Error(
`Failed to fetch restaurants: ${error instanceof Error && error.message}`
`Failed to fetch restaurants: ${error instanceof Error ? error.message : error}`
);
}
}
Expand Down
33 changes: 0 additions & 33 deletions src/restaurants/ui/RestaurantDisplay.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion src/restaurants/ui/RestaurantList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface RestaurantListProps {
}

export const RestaurantList = observer(({ store }: RestaurantListProps) => {
const restaurants = store.restaurantsResource.read();
const restaurants = store.read();
return (
<List>
{restaurants.map(restaurant => (
Expand Down
31 changes: 31 additions & 0 deletions src/restaurants/ui/RestaurantView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ErrorBoundaryWithReset } from '@/ErrorBoundaryWithReset';
import { Box, Skeleton } from '@mui/material';
import { Suspense, useCallback } from 'react';
import { RestaurantList } from './RestaurantList';
import { IRestaurantStore } from '../store/IRestaurantStore';
import { observer } from 'mobx-react-lite';

interface RestaurantViewProps {
store: IRestaurantStore;
}

export const RestaurantView = observer(({ store }: RestaurantViewProps) => {
const handleReset = useCallback(() => {
store.getRestaurants();
}, [store]);

return (
<ErrorBoundaryWithReset onReset={handleReset}>
<Suspense
fallback={
<Box data-testid="restaurant-screen-skeleton-ui">
{Array.from({ length: 15 }).map((_, index) => (
<Skeleton key={index} animation="wave" height={40} />
))}
</Box>
}>
<RestaurantList store={store} />
</Suspense>
</ErrorBoundaryWithReset>
);
});
6 changes: 6 additions & 0 deletions src/utils/create-resource.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { makeAutoObservable } from 'mobx';

export interface Resource<T> {
/**
* Reads the resource's data.
* @throws {Promise<T>} Throws the pending promise if the resource is still loading.
* @throws {Error} Throws an error if the resource encountered an error during fetch.
* @returns {T} Returns the resource data if available.
*/
read: () => T;
update: (promise: Promise<T>) => Promise<T>;
refresh: (data: T) => void;
Expand Down
39 changes: 15 additions & 24 deletions tests/restaurants/RestaurantScreen.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,16 @@
import { render, screen } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import { vi } from 'vitest';
import { mockType } from '../mock-type';
import { RestaurantStoreContext } from '@/restaurants/RestaurantContext';
import { IRestaurantStore } from '@/restaurants/store/IRestaurantStore';
import { RestaurantScreen } from '@/restaurants/RestaurantScreen';
import { mockType } from '../mock-type';
import { IRestaurantStore } from '@/restaurants/store/IRestaurantStore';

vi.mock('@/restaurants/ui/RestaurantDisplay');
vi.mock('@/restaurants/ui/RestaurantList');

describe('RestaurantScreen', () => {
let mockStore: IRestaurantStore;

beforeEach(() => {
mockStore = mockType<IRestaurantStore>({
restaurantsResource: {
read: vi.fn().mockResolvedValue([]),
update: vi.fn().mockReturnValue(new Promise(() => {})),
refresh: vi.fn(),
},
getRestaurants: vi.fn().mockResolvedValueOnce([]),
transportLayer: { get: vi.fn().mockResolvedValue([]), create: vi.fn() },
});
});

it('should render the RestaurantScreen', async () => {
// Arrange
const { view } = setup(mockStore);
const { view } = setup();

// Act
view(<RestaurantScreen />);
Expand All @@ -33,12 +19,17 @@ describe('RestaurantScreen', () => {
expect(screen.getByRole('heading', { name: /restaurants/i })).toBeVisible();
});

function setup(store: IRestaurantStore) {
function setup() {
const view = (component: React.ReactNode) => {
return render(
<RestaurantStoreContext.Provider value={store}>
{component}
</RestaurantStoreContext.Provider>
const mockStore = mockType<IRestaurantStore>({
read: vi.fn().mockResolvedValue([]),
});
return waitFor(() =>
render(
<RestaurantStoreContext.Provider value={mockStore}>
{component}
</RestaurantStoreContext.Provider>
)
);
};
return { view };
Expand Down
6 changes: 3 additions & 3 deletions tests/restaurants/store/RestaurantStore.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('RestaurantStore', () => {

// Act
await sut.getRestaurants();
const actual = sut.restaurantsResource.read();
const actual = sut.read();

// Assert
expect(spy).toHaveBeenCalled();
Expand Down Expand Up @@ -100,7 +100,7 @@ describe('RestaurantStore', () => {

// Act
await sut.createRestaurant({ name: restaurantName });
const actual = sut.restaurantsResource.read();
const actual = sut.read();

// Assert
expect(actual).toContainEqual(expected);
Expand Down Expand Up @@ -161,7 +161,7 @@ describe('RestaurantStore', () => {

// Act
await sut.createRestaurant({ name: restaurantName });
const actual = sut.restaurantsResource.read();
const actual = sut.read();

// Assert
expect(actual).toEqual(expected);
Expand Down
7 changes: 1 addition & 6 deletions tests/restaurants/ui/RestaurantList.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,8 @@ describe('RestaurantList', () => {
id: simpleFaker.number.int(100),
},
];
const mockResource = {
read: vi.fn().mockReturnValue(restaurants),
update: vi.fn(),
refresh: vi.fn(),
};
const mockStore = mockType<IRestaurantStore>({
restaurantsResource: mockResource,
read: vi.fn().mockReturnValue(restaurants),
});

// Act
Expand Down
6 changes: 1 addition & 5 deletions tests/restaurants/ui/RestaurantSavingError.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ describe('RestaurantSavingError', () => {
it('should display the error message', async () => {
// Arrange
mockStore.isSavingError = true;
mockStore.restaurantsResource = {
read: vi.fn(() => []),
update: vi.fn().mockResolvedValueOnce(new Promise(() => {})),
refresh: vi.fn(),
};
mockStore.read = vi.fn(() => []);

// Act
render(<RestaurantSavingError store={mockStore} />);
Expand Down
Loading

0 comments on commit 8b68233

Please sign in to comment.