ModelViewerStepLoading.test.tsx 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. /**
  2. * STEP loading state (#2976).
  3. *
  4. * A large STEP export can take over a minute in the OpenCascade worker. With
  5. * only a spinner on screen that read as a preview that never opens, so a
  6. * STEP load says what it is doing and counts the seconds. Other formats load
  7. * in a moment and keep the bare spinner.
  8. */
  9. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
  10. import { act, render, screen } from '@testing-library/react';
  11. // Only the GL context is faked (jsdom has none); the rest of three.js runs.
  12. vi.mock('three', async (importOriginal) => {
  13. const actual = await importOriginal<typeof import('three')>();
  14. class FakeWebGLRenderer {
  15. domElement = document.createElement('canvas');
  16. shadowMap = { enabled: false, type: 0 };
  17. toneMapping = 0;
  18. toneMappingExposure = 1;
  19. setSize() {}
  20. setPixelRatio() {}
  21. render() {}
  22. dispose() {}
  23. }
  24. class FakePMREMGenerator {
  25. fromScene() {
  26. return { texture: new actual.Texture(), dispose() {} };
  27. }
  28. dispose() {}
  29. }
  30. return { ...actual, WebGLRenderer: FakeWebGLRenderer, PMREMGenerator: FakePMREMGenerator };
  31. });
  32. vi.mock('three/examples/jsm/controls/OrbitControls.js', () => ({
  33. OrbitControls: class {
  34. enableDamping = false;
  35. dampingFactor = 0;
  36. target = { set() {}, copy() {} };
  37. update() {}
  38. dispose() {}
  39. },
  40. }));
  41. import { ModelViewer } from '../../components/ModelViewer';
  42. describe('ModelViewer STEP loading state', () => {
  43. beforeEach(() => {
  44. vi.useFakeTimers();
  45. // Never resolves: the viewer stays in its loading state for the test.
  46. vi.stubGlobal('fetch', vi.fn(() => new Promise(() => {})));
  47. vi.stubGlobal(
  48. 'ResizeObserver',
  49. class {
  50. observe() {}
  51. unobserve() {}
  52. disconnect() {}
  53. },
  54. );
  55. });
  56. afterEach(() => {
  57. vi.useRealTimers();
  58. vi.unstubAllGlobals();
  59. });
  60. it('says a STEP file is converting and counts the seconds', () => {
  61. render(<ModelViewer url="/api/v1/library/files/7/download" fileType="stp" />);
  62. expect(screen.getByText('Converting STEP model… 0 s')).toBeInTheDocument();
  63. expect(screen.getByText('Large STEP files can take a minute or more.')).toBeInTheDocument();
  64. act(() => {
  65. vi.advanceTimersByTime(3000);
  66. });
  67. expect(screen.getByText('Converting STEP model… 3 s')).toBeInTheDocument();
  68. });
  69. it('recognises a .step URL without an explicit file type', () => {
  70. render(<ModelViewer url="/files/housing.step" />);
  71. expect(screen.getByText('Converting STEP model… 0 s')).toBeInTheDocument();
  72. });
  73. it('keeps the bare spinner for formats that load quickly', () => {
  74. render(<ModelViewer url="/api/v1/library/files/8/download" fileType="stl" />);
  75. expect(screen.queryByText(/Converting STEP model/)).not.toBeInTheDocument();
  76. expect(screen.queryByText('Large STEP files can take a minute or more.')).not.toBeInTheDocument();
  77. });
  78. });