Skip to content

Commit

Permalink
gallery view #1081 #1084
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuadkitenge committed Nov 1, 2024
1 parent 0679b60 commit a65b74c
Show file tree
Hide file tree
Showing 16 changed files with 2,585 additions and 3 deletions.
76 changes: 76 additions & 0 deletions cypress/e2e/with_mock_data/items.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,82 @@ describe('Items', () => {

cy.findByText('Upload failed').should('exist');
});

it('falls back to placeholder thumbnail', () => {
cy.window().its('msw').should('not.equal', undefined);
cy.window().then((window) => {
const { worker, http } = window.msw;

worker.use(
http.get('/images', async () => {
return HttpResponse.json(
[
{
id: '1',
thumbnail_base64: 'test',
file_name: 'test.png',
},
],
{ status: 200 }
);
})
);
});

cy.findByText('5YUQDDjKpz2z').click();
cy.findByText(
'High-resolution cameras for beam characterization. 1'
).should('exist');

cy.findByText('Gallery').click();

cy.findByRole('progressbar', { timeout: 10000 }).should('not.exist');

cy.findByRole('img', { timeout: 10000 }).should(
'have.attr',
'src',
'http://localhost:3000/images/thumbnail-not-available.png'
);
});

it('opens full-size image when thumbnail is clicked and navigates to the next image', () => {
cy.findByText('5YUQDDjKpz2z').click();
cy.findByText(
'High-resolution cameras for beam characterization. 1'
).should('exist');

cy.findByText('Gallery').click();

cy.findAllByAltText('Image: tetstw').first().click();

cy.findByText('File Name: stfc-logo-blue-text.png').should('exist');
cy.findByText('Title: tetstw').should('exist');
cy.findByText('test').should('exist');

cy.findByRole('dialog').within(() => {
cy.findByRole('img').should('exist');
});

cy.findByRole('button', { name: 'Next' }).click();

cy.findByText('File Name: logo1.png').should('exist');
cy.findByText('Title: tetstw').should('exist');
cy.findByText('test').should('exist');

cy.findByRole('dialog').within(() => {
cy.findByRole('img').should('exist');
});

cy.findByRole('button', { name: 'Next' }).click();
// Failed to render image
cy.findByText('File Name: stfc-logo-blue-text.png').should('exist');
cy.findByText('Title: tetstw').should('exist');
cy.findByText('test').should('exist');

cy.findByRole('dialog').within(() => {
cy.findByRole('img', { timeout: 10000 }).should('exist');
});
});
});

it('delete an item', () => {
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@
"lz-string": "^1.5.0",
"material-react-table": "^2.13.0",
"msw": "2.4.11",
"photoswipe": "^5.4.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.52.0",
"react-photoswipe-gallery": "^3.0.2",
"react-redux": "^9.1.2",
"react-router-dom": "^6.23.1",
"single-spa-react": "5.1.4",
Expand Down
Binary file added public/images/image-not-available.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/thumbnail-not-available.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/api/api.types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,7 @@ export interface Image
id: string;
thumbnail_base64: string;
}

export interface ImageGet extends Image {
download_url: string;
}
23 changes: 23 additions & 0 deletions src/api/images.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { renderHook, waitFor } from '@testing-library/react';
import { hooksWrapperWithProviders } from '../testUtils';
import { useGetImages } from './images';

describe('images api functions', () => {
afterEach(() => {
vi.clearAllMocks();
});

describe('useGetImages', () => {
it('sends request to fetch image data and returns successful response', async () => {
const { result } = renderHook(() => useGetImages('1'), {
wrapper: hooksWrapperWithProviders(),
});

await waitFor(() => {
expect(result.current.isSuccess).toBeTruthy();
});

expect(result.current.data?.length).toEqual(20);
});
});
});
30 changes: 30 additions & 0 deletions src/api/images.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useQuery, UseQueryResult } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import { storageApi } from './api';
import { Image, ImageGet } from './api.types';

export const getImage = async (id: string): Promise<ImageGet> => {
return storageApi.get(`/images/${id}`).then((response) => {
return response.data;
});
};

const getImages = async (entityId: string): Promise<Image[]> => {
const queryParams = new URLSearchParams();
queryParams.append('entity_id', entityId);
return storageApi
.get(`/images`, {
params: queryParams,
})
.then((response) => response.data);
};

export const useGetImages = (
entityId?: string
): UseQueryResult<Image[], AxiosError> => {
return useQuery({
queryKey: ['Images', entityId],
queryFn: () => getImages(entityId ?? ''),
enabled: !!entityId,
});
};
1 change: 0 additions & 1 deletion src/common/actionMenu.component.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import '@testing-library/jest-dom';
import { screen, waitFor } from '@testing-library/react';
import userEvent, { UserEvent } from '@testing-library/user-event';
import { vi } from 'vitest';
Expand Down
1,856 changes: 1,856 additions & 0 deletions src/common/images/__snapshots__/imageGallery.component.test.tsx.snap

Large diffs are not rendered by default.

211 changes: 211 additions & 0 deletions src/common/images/imageGallery.component.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import {
cleanup,
fireEvent,
screen,
waitFor,
within,
} from '@testing-library/react';
import userEvent, { UserEvent } from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
import { act } from 'react';
import { MockInstance } from 'vitest';
import { storageApi } from '../../api/api';
import ImageJSON from '../../mocks/image.json';
import { server } from '../../mocks/server';
import { renderComponentWithRouterProvider } from '../../testUtils';
import ImageGallery, { ImageGalleryProps } from './imageGallery.component';

describe('Image Gallery', () => {
let props: ImageGalleryProps;
let user: UserEvent;
let axiosGetSpy: MockInstance;

const createView = () => {
return renderComponentWithRouterProvider(<ImageGallery {...props} />);
};

beforeAll(() => {
let _src: string;

Object.defineProperty(global.Image.prototype, 'src', {
set(value) {
_src = value;

// Check for an invalid base64 thumbnail or URL and call onError
if (value.includes('test')) {
setTimeout(() => {
if (typeof this.onerror === 'function') {
this.onerror(new Event('error'));
}
}, 0);
} else {
setTimeout(() => {
if (typeof this.onload === 'function') {
this.onload();
}
}, 0);
}
},
get() {
return _src;
},
});

Object.defineProperty(global.Image.prototype, 'naturalWidth', {
get() {
return 100;
},
});

Object.defineProperty(global.Image.prototype, 'naturalHeight', {
get() {
return 100;
},
});
});

beforeEach(() => {
props = {
entityId: '1',
};
user = userEvent.setup();
axiosGetSpy = vi.spyOn(storageApi, 'get');
});

afterEach(() => {
vi.clearAllMocks();
cleanup();
});

it('renders correctly', async () => {
let baseElement;
await act(async () => {
baseElement = createView().baseElement;
});

await waitFor(() =>
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()
);

expect(screen.getAllByText('logo1.png').length).toEqual(10);
expect(baseElement).toMatchSnapshot();
});

it('renders no results page correctly', async () => {
server.use(
http.get('/images', async () => {
return HttpResponse.json([], { status: 200 });
})
);
let baseElement;
await act(async () => {
baseElement = createView().baseElement;
});

await waitFor(() =>
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()
);

expect(screen.queryByText('logo1.png')).not.toBeInTheDocument();
expect(baseElement).toMatchSnapshot();
});

it('falls back to placeholder thumbnail', async () => {
server.use(
http.get('/images', async () => {
return HttpResponse.json(
[
{
...ImageJSON,
id: '1',
thumbnail_base64: 'test',
file_name: 'test.png',
},
],
{ status: 200 }
);
})
);
createView();

await waitFor(() =>
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()
);

const image = screen.getByRole('img') as HTMLImageElement; // Replace with actual alt text or selector

expect(image).toHaveAttribute('src', 'data:image/webp;base64,test');
fireEvent.error(image);

await waitFor(() => {
expect(image.src).toEqual('/images/thumbnail-not-available.png');
});
});

it('opens full-size image when thumbnail is clicked, navigates to the next image, and then navigates to a third image that failed to upload, falling back to a placeholder', async () => {
createView();

await waitFor(() =>
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()
);
const thumbnail = await screen.findAllByAltText('Image: tetstw');
await user.click(thumbnail[0]);

expect(axiosGetSpy).toHaveBeenCalledWith('/images/1');
await waitFor(() => {
expect(
screen.getByText('File Name: stfc-logo-blue-text.png')
).toBeInTheDocument();
});
expect(screen.getByText('Title: tetstw')).toBeInTheDocument();
expect(screen.getByText('test')).toBeInTheDocument();

await waitFor(
() => {
expect(
within(screen.getByRole('dialog')).getByRole('img')
).toBeInTheDocument();
},
{ timeout: 5000 }
);

await user.click(screen.getByRole('button', { name: 'Next' }));

expect(axiosGetSpy).toHaveBeenCalledWith('/images/2');
await waitFor(() => {
expect(screen.getByText('File Name: logo1.png')).toBeInTheDocument();
});
expect(screen.getByText('Title: tetstw')).toBeInTheDocument();
expect(screen.getByText('test')).toBeInTheDocument();

await waitFor(
() => {
expect(
within(screen.getByRole('dialog')).getByRole('img')
).toBeInTheDocument();
},
{ timeout: 5000 }
);
await user.click(screen.getByRole('button', { name: 'Next' }));

// Failed to render image
expect(axiosGetSpy).toHaveBeenCalledWith('/images/3');

await waitFor(() => {
expect(
screen.getByText('File Name: stfc-logo-blue-text.png')
).toBeInTheDocument();
});
expect(screen.getByText('Title: tetstw')).toBeInTheDocument();
expect(screen.getByText('test')).toBeInTheDocument();

await waitFor(
() => {
expect(
within(screen.getByRole('dialog')).getByRole('img')
).toBeInTheDocument();
},
{ timeout: 5000 }
);
}, 15000);
});
Loading

0 comments on commit a65b74c

Please sign in to comment.