wrolpi/app/src/components/inventory/InventoryImportModal.test.js
Roland 929534e455
ui: thirty-five files off Semantic, leaving fifteen
Phase 2, part 4.  Baseline 50 -> 15.

Migrated: all eight calculators, the rest of inventory and collections, the
Map/Zim/Docs cluster, Videos, Files, Search, Archive, DashboardPage,
DatesSelector, HelpModal, Events, Tags, Flasher's neighbours, admin Settings
and Status, Download, and App.

Also the three shared modules everything imports: api.js and customHooks.js
swap the Semantic toast library for the library's shim (same call shape), and
useForm.js renders the library's Message.  useForm accepts both the old
`{content, header, positive|negative}` shape and the new `{kind, title}`,
because the pages building those objects migrate separately.

Two library gaps closed, both found by call sites needing them:

  ActionInput is a forwardRef now.  Its ref reaches the input rather than the
  wrapper, because callers use it to focus or select the field -- a ref to the
  surrounding div would do neither, which is why one call site had resorted to
  hand-copying the component's markup.

  Header takes a `color` token name.  Three separate call sites had reached for
  an inline style to colour a heading that carries meaning ("3 items to add"
  against "2 to remove"), which is how a hex ends up in markup.

Two files needed repair after being interrupted mid-edit: MapViewer had a
Segment left in its error branch, and VideoPlayer had lost the useContext
import its own code still used.  Neither jest nor tsc noticed; only
`npm run build` did, which is the whole reason that check exists.

Fifteen files remain: Archive, BulkTagModal, Channels, Docs, FileBrowser,
FilePreview, Flasher, Nav, Playlists, Theme.tsx, Upload, VideoPlayer, Zim,
ControllerPage, admin/Downloads.  Theme.tsx is deliberately last -- the
unmigrated files still import its Semantic-backed wrappers, so it can only go
once they have.
2026-08-05 18:34:58 -06:00

68 lines
3.1 KiB
JavaScript

import React from "react";
import {fireEvent, render, screen, waitFor, within} from "../../test-utils";
jest.mock("../../api", () => ({
getInventoryBackups: jest.fn(),
postInventoryRestorePreview: jest.fn(),
postInventoryRestore: jest.fn(),
reimportInventories: jest.fn(),
}));
import {InventoryImportModal} from "./InventoryImportModal";
import {getInventoryBackups, postInventoryRestore, postInventoryRestorePreview, reimportInventories} from "../../api";
const renderModal = (props = {}) => {
const onChanged = jest.fn(() => Promise.resolve());
const onClose = jest.fn();
render(<InventoryImportModal open slug='food-storage' name='Food Storage'
onChanged={onChanged} onClose={onClose} {...props}/>);
return {onChanged, onClose};
};
describe('InventoryImportModal', () => {
// CRA's jest config resets mock implementations before each test, so (re)set them here.
beforeEach(() => {
getInventoryBackups.mockResolvedValue(['20260115', '20260101']);
postInventoryRestorePreview.mockResolvedValue({
mode: 'merge', add: [{id: 2, name: 'Beans'}], remove: [], unchanged: 1,
fields_change: false, current_count: 1, backup_count: 2, backup_name: 'Food Storage',
});
postInventoryRestore.mockResolvedValue({slug: 'food-storage', items: []});
reimportInventories.mockResolvedValue([{slug: 'food-storage'}]);
});
test('lists backups and applies a merge restore', async () => {
const {onChanged, onClose} = renderModal();
// Backup dates load (newest first).
const row = (await screen.findByText('2026-01-15')).closest('tr');
fireEvent.click(within(row).getByRole('button', {name: /merge/i}));
// Preview is requested for that date/mode, then the confirmation view appears.
await waitFor(() => expect(postInventoryRestorePreview)
.toHaveBeenCalledWith('food-storage', '20260115', 'merge'));
await screen.findByText(/Merge backup from 2026-01-15/);
expect(screen.getByText(/1 to add/)).toBeInTheDocument();
expect(screen.getByText('Beans')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', {name: /apply/i}));
await waitFor(() => expect(postInventoryRestore)
.toHaveBeenCalledWith('food-storage', '20260115', 'merge'));
await waitFor(() => expect(onChanged).toHaveBeenCalled());
await waitFor(() => expect(onClose).toHaveBeenCalled());
});
test('re-import from disk reloads inventories', async () => {
const {onChanged} = renderModal();
await screen.findByText('2026-01-15'); // wait for initial load to settle
fireEvent.click(screen.getByRole('button', {name: /re-import from disk/i}));
await waitFor(() => expect(reimportInventories).toHaveBeenCalled());
await waitFor(() => expect(onChanged).toHaveBeenCalled());
});
test('fetches backups for the inventory on open', async () => {
renderModal();
await waitFor(() => expect(getInventoryBackups).toHaveBeenCalledWith('food-storage'));
});
});