| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- /**
- * Tests for the copy-spool button in InventoryPage.
- *
- * Three callsites — table-row, card, and grouped-view inner row — each wire
- * onCopy from the page-level setFormModal({ spool, mode: 'copy' }) state.
- * These tests cover the two visually distinct components (SpoolTableRow and
- * SpoolCard). The grouped-view path is SpoolTableGroup which renders inner
- * SpoolTableRow rows with onCopy={onCopy ? () => onCopy(spool) : undefined} —
- * a one-line forward of the same callback the table-row test already
- * exercises end-to-end.
- */
- import { describe, it, expect, beforeEach } from 'vitest';
- import { screen, waitFor, fireEvent } from '@testing-library/react';
- import { render } from '../utils';
- import InventoryPageRouter from '../../pages/InventoryPage';
- import { http, HttpResponse } from 'msw';
- import { server } from '../mocks/server';
- const baseSpool = {
- subtype: null,
- brand: 'eSun',
- color_name: 'Blue',
- rgba: '0000FFFF',
- extra_colors: null,
- effect_type: null,
- label_weight: 1000,
- core_weight: 250,
- core_weight_catalog_id: null,
- slicer_filament: null,
- slicer_filament_name: null,
- nozzle_temp_min: null,
- nozzle_temp_max: null,
- note: null,
- added_full: null,
- last_used: null,
- encode_time: null,
- tag_uid: null,
- tray_uuid: null,
- data_origin: null,
- tag_type: null,
- archived_at: null,
- created_at: '2025-01-01T00:00:00Z',
- updated_at: '2025-01-01T00:00:00Z',
- k_profiles: [] as never[],
- cost_per_kg: null,
- last_scale_weight: null,
- last_weighed_at: null,
- storage_location: null,
- category: null,
- low_stock_threshold_pct: null,
- spoolman_id: null,
- spoolman_filament_id: null,
- };
- const MOCK_SPOOL = {
- ...baseSpool,
- id: 5,
- material: 'PETG',
- weight_used: 400,
- };
- const MOCK_SETTINGS = {
- auto_archive: false,
- save_thumbnails: false,
- capture_finish_photo: false,
- default_filament_cost: 25.0,
- currency: 'USD',
- energy_cost_per_kwh: 0.15,
- energy_tracking_mode: 'total',
- spoolman_enabled: false,
- spoolman_url: '',
- spoolman_sync_mode: 'auto',
- spoolman_disable_weight_sync: false,
- spoolman_report_partial_usage: true,
- check_updates: false,
- check_printer_firmware: false,
- include_beta_updates: false,
- language: 'en',
- notification_language: 'en',
- bed_cooled_threshold: 35,
- ams_humidity_good: 40,
- ams_humidity_fair: 60,
- ams_temp_good: 28,
- ams_temp_fair: 35,
- ams_history_retention_days: 30,
- per_printer_mapping_expanded: false,
- date_format: 'system',
- time_format: 'system',
- default_printer_id: null,
- virtual_printer_enabled: false,
- virtual_printer_access_code: '',
- virtual_printer_mode: 'immediate',
- dark_style: 'classic',
- dark_background: 'neutral',
- dark_accent: 'green',
- light_style: 'classic',
- light_background: 'neutral',
- light_accent: 'green',
- ftp_retry_enabled: true,
- ftp_retry_count: 3,
- ftp_retry_delay: 2,
- ftp_timeout: 30,
- mqtt_enabled: false,
- mqtt_broker: '',
- mqtt_port: 1883,
- mqtt_username: '',
- mqtt_password: '',
- mqtt_topic_prefix: 'bambuddy',
- mqtt_use_tls: false,
- external_url: '',
- ha_enabled: false,
- ha_url: '',
- ha_token: '',
- ha_url_from_env: false,
- ha_token_from_env: false,
- ha_env_managed: false,
- library_archive_mode: 'ask',
- library_disk_warning_gb: 5.0,
- camera_view_mode: 'window',
- preferred_slicer: 'bambu_studio',
- prometheus_enabled: false,
- prometheus_token: '',
- low_stock_threshold: 20.0,
- };
- function setupHandlers(spools: unknown[] = [MOCK_SPOOL]) {
- server.use(
- http.get('/api/v1/settings/', () => HttpResponse.json(MOCK_SETTINGS)),
- http.get('/api/v1/settings/spoolman', () =>
- HttpResponse.json({
- spoolman_enabled: 'false',
- spoolman_url: '',
- spoolman_sync_mode: 'auto',
- spoolman_disable_weight_sync: 'false',
- spoolman_report_partial_usage: 'true',
- })
- ),
- http.get('/api/v1/inventory/spools', () => HttpResponse.json(spools)),
- http.get('/api/v1/inventory/assignments', () => HttpResponse.json([])),
- http.get('/api/v1/inventory/catalog', () => HttpResponse.json([])),
- // SpoolFormModal kicks off these fetches the moment it opens. Without
- // handlers MSW would passthrough to the real network and ECONNREFUSED;
- // those promises then resolve after the test environment is torn down,
- // surfacing as an unhandled rejection in the modal's setState finally.
- http.get('/api/v1/cloud/status', () =>
- HttpResponse.json({ is_authenticated: false })
- ),
- http.get('/api/v1/cloud/local-presets', () =>
- HttpResponse.json({ filament: [], printer: [], process: [] })
- ),
- http.get('/api/v1/local-presets/', () =>
- HttpResponse.json({ filament: [], printer: [], process: [] })
- ),
- http.get('/api/v1/cloud/builtin-filaments', () => HttpResponse.json([])),
- http.get('/api/v1/inventory/color-catalog', () => HttpResponse.json([])),
- http.get('/api/v1/inventory/colors', () => HttpResponse.json([])),
- http.get('/api/v1/inventory/spool-catalog', () => HttpResponse.json([])),
- http.get('/api/v1/printers/', () => HttpResponse.json([])),
- );
- }
- describe('InventoryPage — copy button', () => {
- beforeEach(() => {
- setupHandlers();
- });
- it('opens SpoolFormModal in "Copy Spool" mode when the copy button in the table row is clicked', async () => {
- render(<InventoryPageRouter />);
- // Wait for the spool list to render
- await waitFor(() => {
- expect(screen.getAllByText('PETG').length).toBeGreaterThan(0);
- });
- // Find the "Copy Spool" button (title attribute) in the table row
- const copyButtons = await screen.findAllByTitle('Copy Spool');
- expect(copyButtons.length).toBeGreaterThan(0);
- // Click the first copy button (table view is default)
- fireEvent.click(copyButtons[0]);
- // The modal should open with the "Copy Spool" heading
- await waitFor(() => {
- expect(screen.getByRole('heading', { name: 'Copy Spool' })).toBeInTheDocument();
- });
- });
- it('opens SpoolFormModal in "Copy Spool" mode when the copy button in the cards view is clicked', async () => {
- render(<InventoryPageRouter />);
- await waitFor(() => {
- expect(screen.getAllByText('PETG').length).toBeGreaterThan(0);
- });
- // Switch to cards view
- fireEvent.click(screen.getByRole('button', { name: /^Cards$/ }));
- // The card-view copy button has the same title; wait for the card render
- // to settle, then click it.
- const copyButtons = await screen.findAllByTitle('Copy Spool');
- expect(copyButtons.length).toBeGreaterThan(0);
- fireEvent.click(copyButtons[0]);
- await waitFor(() => {
- expect(screen.getByRole('heading', { name: 'Copy Spool' })).toBeInTheDocument();
- });
- });
- });
|