|
|
@@ -11,6 +11,7 @@
|
|
|
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
|
import { screen, waitFor } from '@testing-library/react';
|
|
|
+import userEvent from '@testing-library/user-event';
|
|
|
import { http, HttpResponse } from 'msw';
|
|
|
import { render } from '../utils';
|
|
|
import { server } from '../mocks/server';
|
|
|
@@ -32,6 +33,24 @@ function visit(search: string) {
|
|
|
|
|
|
const viewerUrl = () => screen.getByTestId('toolpath-viewer').getAttribute('data-url');
|
|
|
|
|
|
+const plate = (index: number, color: string) => ({
|
|
|
+ index,
|
|
|
+ name: null,
|
|
|
+ objects: [],
|
|
|
+ has_thumbnail: false,
|
|
|
+ thumbnail_url: null,
|
|
|
+ print_time_seconds: null,
|
|
|
+ filament_used_grams: null,
|
|
|
+ filaments: [{ slot_id: 1, type: 'PLA', color, used_grams: 1, used_meters: 1 }],
|
|
|
+});
|
|
|
+
|
|
|
+const TWO_PLATES = {
|
|
|
+ file_id: 7,
|
|
|
+ filename: 'two.gcode.3mf',
|
|
|
+ is_multi_plate: true,
|
|
|
+ plates: [plate(1, '#ff0000'), plate(2, '#00ff00')],
|
|
|
+};
|
|
|
+
|
|
|
describe('GCodeViewerPage', () => {
|
|
|
const originalUrl = window.location.href;
|
|
|
|
|
|
@@ -82,6 +101,56 @@ describe('GCodeViewerPage', () => {
|
|
|
});
|
|
|
});
|
|
|
|
|
|
+describe('GCodeViewerPage — plate switcher', () => {
|
|
|
+ const originalUrl = window.location.href;
|
|
|
+ afterEach(() => window.history.pushState({}, '', originalUrl));
|
|
|
+
|
|
|
+ it('offers every plate of a multi-plate file and shows the one being previewed', async () => {
|
|
|
+ // Nothing that opens this page from the File Manager passes a plate, so
|
|
|
+ // without a switcher the other plates of a sliced multi-plate 3MF were
|
|
|
+ // simply unreachable.
|
|
|
+ server.use(http.get('/api/v1/library/files/:id/plates', () => HttpResponse.json(TWO_PLATES)));
|
|
|
+
|
|
|
+ window.history.pushState({}, '', '/gcode-viewer?library_file=7');
|
|
|
+ render(<GCodeViewerPage />);
|
|
|
+
|
|
|
+ const first = await screen.findByRole('button', { name: /Plate 1/ });
|
|
|
+ const second = screen.getByRole('button', { name: /Plate 2/ });
|
|
|
+ // No plate in the URL means the backend serves the lowest-numbered one,
|
|
|
+ // which is what the switcher has to agree with.
|
|
|
+ expect(first).toHaveAttribute('aria-pressed', 'true');
|
|
|
+ expect(second).toHaveAttribute('aria-pressed', 'false');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('asks for the plate that was picked', async () => {
|
|
|
+ server.use(http.get('/api/v1/library/files/:id/plates', () => HttpResponse.json(TWO_PLATES)));
|
|
|
+
|
|
|
+ const user = userEvent.setup();
|
|
|
+ window.history.pushState({}, '', '/gcode-viewer?library_file=7');
|
|
|
+ render(<GCodeViewerPage />);
|
|
|
+
|
|
|
+ await user.click(await screen.findByRole('button', { name: /Plate 2/ }));
|
|
|
+
|
|
|
+ await waitFor(() => expect(viewerUrl()).toContain('plate=2'));
|
|
|
+ // The choice lives in the URL, so the view survives a reload or a share.
|
|
|
+ expect(window.location.search).toContain('plate=2');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('stays out of the way for a single-plate file', async () => {
|
|
|
+ server.use(
|
|
|
+ http.get('/api/v1/library/files/:id/plates', () =>
|
|
|
+ HttpResponse.json({ ...TWO_PLATES, is_multi_plate: false, plates: [plate(1, '#ff0000')] }),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+
|
|
|
+ window.history.pushState({}, '', '/gcode-viewer?library_file=7');
|
|
|
+ render(<GCodeViewerPage />);
|
|
|
+
|
|
|
+ await waitFor(() => expect(screen.getByTestId('toolpath-viewer')).toBeInTheDocument());
|
|
|
+ expect(screen.queryByRole('button', { name: /Plate 1/ })).not.toBeInTheDocument();
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
describe('GCodeViewerPage — filament colours', () => {
|
|
|
const originalUrl = window.location.href;
|
|
|
afterEach(() => window.history.pushState({}, '', originalUrl));
|
|
|
@@ -123,6 +192,17 @@ describe('GCodeViewerPage — filament colours', () => {
|
|
|
);
|
|
|
});
|
|
|
|
|
|
+ it('colours from the plate being previewed, not the first one', async () => {
|
|
|
+ // Plate 2's toolpath rendered in plate 1's colours is the same wrong-plate
|
|
|
+ // mistake one layer up: both have to follow the `plate` parameter.
|
|
|
+ server.use(http.get('/api/v1/library/files/:id/plates', () => HttpResponse.json(TWO_PLATES)));
|
|
|
+
|
|
|
+ window.history.pushState({}, '', '/gcode-viewer?library_file=7&plate=2');
|
|
|
+ render(<GCodeViewerPage />);
|
|
|
+
|
|
|
+ await waitFor(() => expect(screen.getByTestId('toolpath-viewer')).toHaveAttribute('data-colors', '#00ff00'));
|
|
|
+ });
|
|
|
+
|
|
|
it('previews without colours when the plate metadata carries none', async () => {
|
|
|
server.use(
|
|
|
http.get('/api/v1/library/files/:id/plates', () =>
|