diff --git a/specifyweb/frontend/js_src/lib/components/Attachments/__tests__/AttachmentCell.test.tsx b/specifyweb/frontend/js_src/lib/components/Attachments/__tests__/AttachmentCell.test.tsx index 2271d67b4a2..762c2dccfda 100644 --- a/specifyweb/frontend/js_src/lib/components/Attachments/__tests__/AttachmentCell.test.tsx +++ b/specifyweb/frontend/js_src/lib/components/Attachments/__tests__/AttachmentCell.test.tsx @@ -8,7 +8,7 @@ import attachmentSettings from '../../../tests/ajax/static/context/attachment_se import { requireContext } from "../../../tests/helpers"; import { mount } from "../../../tests/reactUtils"; import { f } from "../../../utils/functools"; -import { type GetOrSet, type GetSet,overwriteReadOnly } from "../../../utils/types"; +import { type GetSet, overwriteReadOnly } from "../../../utils/types"; import { LoadingContext } from "../../Core/Contexts"; import type { AnySchema, SerializedResource } from "../../DataModel/helperTypes"; import type { SpecifyResource } from "../../DataModel/legacyTypes"; @@ -17,48 +17,22 @@ import { tables } from "../../DataModel/tables"; import type { Attachment } from "../../DataModel/types"; import { overrideAttachmentSettings } from "../attachments"; import { AttachmentCell } from "../Cell"; +import { makeUseAsyncRef, testStaticResources } from "./utils" requireContext(); const setStateMock = jest.fn(); +const { testAttachment, testCollectionObjectAttachment } = testStaticResources; + beforeEach(() => { setStateMock.mockClear(); clearIdStore(); }); -jest.spyOn(AsyncState, 'useAsyncState').mockImplementation(useAsyncRef); - -function useAsyncRef( - callback: () => Promise | T | undefined, - _: boolean -): GetOrSet { - const ref = React.useRef(undefined); - - React.useLayoutEffect(() => { - ref.current = undefined; - - Promise.resolve(callback()).then((data) => { - if (destructorCalled) return; - ref.current = data; - setStateMock(data); - }); - - let destructorCalled = false; - - return () => { - destructorCalled = true; - } - }, [callback]); - - const setCallback = (...value: Parameters[1]>) => { - const setter = value[0]; - ref.current = typeof setter === 'function' ? (setter as Function)(ref.current) : setter; - } - - return [ref.current, setCallback]; -} +const useAsyncRef = makeUseAsyncRef(setStateMock); +jest.spyOn(AsyncState, 'useAsyncState').mockImplementation(useAsyncRef); function AttachmentCellMock({ options, ...rest }: { readonly options: { @@ -81,93 +55,10 @@ function AttachmentCellMock({ options, ...rest }: { } describe("AttachmentCell", () => { - const attachment = { - "id": 5490, - "attachmentLocation": "a6daad6c-5091-4461-86ee-eeba9434ef2b.jpg", - "attachmentStorageConfig": null, - "captureDevice": null, - "copyrightDate": null, - "copyrightHolder": null, - "credit": null, - "dateImaged": null, - "fileCreatedDate": null, - "guid": "7664fc1b-cc39-4f7d-b81c-645fc11e8e1d", - "isPublic": true, - "license": null, - "licenseLogoUrl": null, - "metadataText": null, - "mimeType": "image/jpeg", - "origFilename": "topbrc4.jpg", - "remarks": null, - "scopeID": null, - "scopeType": null, - "subjectOrientation": null, - "subtype": null, - "tableID": 1, - "timestampCreated": "2015-05-20T12:16:38", - "timestampModified": "2015-05-20T12:16:38", - "title": "kubiodiversitylogo.jpg", - "type": null, - "version": 0, - "visibility": null, - "attachmentImageAttribute": null, - "createdByAgent": "/api/specify/agent/3/", - "creator": null, - "modifiedByAgent": null, - "visibilitySetBy": null, - "accessionAttachments": "/api/specify/accessionattachment/?attachment=5490", - "agentAttachments": "/api/specify/agentattachment/?attachment=5490", - "metadata": "/api/specify/attachmentmetadata/?attachment=5490", - "tags": "/api/specify/attachmenttag/?attachment=5490", - "borrowAttachments": "/api/specify/borrowattachment/?attachment=5490", - "collectingEventAttachments": "/api/specify/collectingeventattachment/?attachment=5490", - "collectingTripAttachments": "/api/specify/collectingtripattachment/?attachment=5490", - "collectionObjectAttachments": "/api/specify/collectionobjectattachment/?attachment=5490", - "conservDescriptionAttachments": "/api/specify/conservdescriptionattachment/?attachment=5490", - "conservEventAttachments": "/api/specify/conserveventattachment/?attachment=5490", - "dnaSequenceAttachments": "/api/specify/dnasequenceattachment/?attachment=5490", - "dnaSequencingRunAttachments": "/api/specify/dnasequencingrunattachment/?attachment=5490", - "deaccessionAttachments": "/api/specify/deaccessionattachment/?attachment=5490", - "disposalAttachments": "/api/specify/disposalattachment/?attachment=5490", - "exchangeInAttachments": "/api/specify/exchangeinattachment/?attachment=5490", - "exchangeOutAttachments": "/api/specify/exchangeoutattachment/?attachment=5490", - "fieldNotebookAttachments": "/api/specify/fieldnotebookattachment/?attachment=5490", - "fieldNotebookPageAttachments": "/api/specify/fieldnotebookpageattachment/?attachment=5490", - "fieldNotebookPageSetAttachments": "/api/specify/fieldnotebookpagesetattachment/?attachment=5490", - "giftAttachments": "/api/specify/giftattachment/?attachment=5490", - "loanAttachments": "/api/specify/loanattachment/?attachment=5490", - "localityAttachments": "/api/specify/localityattachment/?attachment=5490", - "permitAttachments": "/api/specify/permitattachment/?attachment=5490", - "preparationAttachments": "/api/specify/preparationattachment/?attachment=5490", - "referenceWorkAttachments": "/api/specify/referenceworkattachment/?attachment=5490", - "repositoryAgreementAttachments": "/api/specify/repositoryagreementattachment/?attachment=5490", - "storageAttachments": "/api/specify/storageattachment/?attachment=5490", - "taxonAttachments": "/api/specify/taxonattachment/?attachment=5490", - "treatmentEventAttachments": "/api/specify/treatmenteventattachment/?attachment=5490", - "spDataSetAttachments": "/api/specify/spdatasetattachment/?attachment=5490", - "resource_uri": "/api/specify/attachment/5490/", - "_tableName": "Attachment" - } as const; - - const collectionObjectAttachment = { - "id": 4732, - "collectionmemberid": 4, - "ordinal": 0, - "remarks": null, - "timestampcreated": "2025-07-31T17:00:40", - "timestampmodified": "2025-07-31T17:00:40", - "version": 0, - attachment, - "collectionobject": "/api/specify/collectionobject/51731/", - "createdbyagent": "/api/specify/agent/3/", - "modifiedbyagent": null, - "resource_uri": "/api/specify/collectionobjectattachment/4732/" - } - overrideAjax("/api/specify/collectionobjectattachment/?limit=20&attachment=5490", { "objects": [ - collectionObjectAttachment + testCollectionObjectAttachment ], "meta": { "limit": 20, @@ -190,7 +81,7 @@ describe("AttachmentCell", () => { const { asFragment, rerender, getAllByRole, user } = mount( { rerender( { + setStateMock.mockClear(); + clearIdStore(); +}); + +const oldSetState = React.useState; + + +function useStateMock(defaultValue: unknown) { + const [state, rawSetState] = oldSetState(defaultValue); + const setState = React.useCallback((value: unknown | ((oldValue: unknown) => unknown)) => { + act(() => rawSetState(value)) + }, [rawSetState]); + return [state, setState] as const; +} + +const useAsyncRef = makeUseAsyncRef(setStateMock); +// jest.spyOn(Resource, 'resourceOn').mockImplementation(resourceOnMock); +jest.spyOn(AsyncState, 'useAsyncState').mockImplementation(useAsyncRef); +jest.spyOn(React, 'useState').mockImplementation(useStateMock as typeof oldSetState); + +describe("AttachmentDialog", () => { + + overrideAjax("/context/view.json?name=ObjectAttachment&quiet=", { + "name": "ObjectAttachment", + "class": "edu.ku.brc.specify.datamodel.ObjectAttachmentIFace", + "busrules": "edu.ku.brc.specify.datamodel.busrules.AttachmentBusRules", + "resourcelabels": "false", + "altviews": { + "ObjectAttachment Icon View": { + "name": "ObjectAttachment Icon View", + "viewdef": "ObjectAttachment IconView", + "mode": "view" + }, + "ObjectAttachment Icon Edit": { + "name": "ObjectAttachment Icon Edit", + "viewdef": "ObjectAttachment IconView", + "mode": "edit" + }, + "ObjectAttachment Table View": { + "name": "ObjectAttachment Table View", + "viewdef": "ObjectAttachment Table", + "mode": "view" + }, + "ObjectAttachment Table Edit": { + "name": "ObjectAttachment Table Edit", + "viewdef": "ObjectAttachment Table", + "mode": "edit" + }, + "ObjectAttachment Form View": { + "name": "ObjectAttachment Form View", + "viewdef": "ObjectAttachment Form", + "label": "Form", + "mode": "view", + "default": "true" + }, + "ObjectAttachment Form Edit": { + "name": "ObjectAttachment Form Edit", + "viewdef": "ObjectAttachment Form", + "label": "Form", + "mode": "edit" + } + }, + "viewdefs": { + "ObjectAttachment Table": "\u003Cviewdef type=\"formtable\" name=\"ObjectAttachment Table\" class=\"edu.ku.brc.specify.datamodel.ObjectAttachmentIFace\" gettable=\"edu.ku.brc.af.ui.forms.DataGetterForObj\" settable=\"edu.ku.brc.af.ui.forms.DataSetterForObj\"\u003E\n\t\t\t\u003Cdesc\u003EObjectAttachment grid view.\u003C/desc\u003E\n\t\t\t\u003Cdefinition\u003EObjectAttachment Form\u003C/definition\u003E\n\t\t\u003C/viewdef\u003E\n\t\t", + "ObjectAttachment Form": "\u003Cviewdef type=\"form\" name=\"ObjectAttachment Form\" class=\"edu.ku.brc.specify.datamodel.ObjectAttachmentIFace\" gettable=\"edu.ku.brc.af.ui.forms.DataGetterForObj\" settable=\"edu.ku.brc.af.ui.forms.DataSetterForObj\" useresourcelabels=\"true\"\u003E\n\t\t\t\u003Cdesc\u003EThe ObjectAttachment form.\u003C/desc\u003E\n\t\t\t\u003CcolumnDef\u003Ep,2px,p:g\u003C/columnDef\u003E\n\t\t\t\u003CrowDef auto=\"true\" cell=\"p\" sep=\"2px\" /\u003E\n\t\t\t\u003Crows\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"origFilename\" label=\"FILENAME\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"origFilename\" name=\"attachment.origFilename\" initialize=\"editoncreate=true\" uitype=\"browse\" isrequired=\"true\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"title\" label=\"TITLE\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"title\" name=\"attachment.title\" uitype=\"text\" isrequired=\"true\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"ispub\" label=\"\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"ispub\" name=\"attachment.isPublic\" uitype=\"checkbox\" label=\"Make Public\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003C!-- \u003Crow\u003E\n \t\t \u003Ccell type=\"subview\" id=\"metadata\" name=\"attachment.metadata\" viewname=\"AttachmentMetadata\" colspan=\"3\"/\u003E\n \t\t \u003C/row\u003E --\u003E\n\t\t\t\u003C/rows\u003E\n\t\t\u003C/viewdef\u003E\n\t\t", + "ObjectAttachment IconView": "\u003Cviewdef type=\"iconview\" name=\"ObjectAttachment IconView\" class=\"edu.ku.brc.specify.datamodel.ObjectAttachmentIFace\" gettable=\"edu.ku.brc.af.ui.forms.DataGetterForObj\" settable=\"edu.ku.brc.af.ui.forms.DataSetterForObj\"\u003E\n\t\t\t\u003Cdesc\u003EThe ObjectAttachment Icon Viewer\u003C/desc\u003E\n\t\t\u003C/viewdef\u003E\n\t\t" + }, + "view": "\u003Cview name=\"ObjectAttachment\" class=\"edu.ku.brc.specify.datamodel.ObjectAttachmentIFace\" busrules=\"edu.ku.brc.specify.datamodel.busrules.AttachmentBusRules\" resourcelabels=\"false\"\u003E\n\t\t\t\u003Cdesc\u003EThe Object-Attachment View.\u003C/desc\u003E\n\t\t\t\u003Caltviews\u003E\n\t\t\t\t\u003Caltview name=\"ObjectAttachment Icon View\" viewdef=\"ObjectAttachment IconView\" mode=\"view\" /\u003E\n\t\t\t\t\u003Caltview name=\"ObjectAttachment Icon Edit\" viewdef=\"ObjectAttachment IconView\" mode=\"edit\" /\u003E\n\t\t\t\t\u003Caltview name=\"ObjectAttachment Table View\" viewdef=\"ObjectAttachment Table\" mode=\"view\" /\u003E\n\t\t\t\t\u003Caltview name=\"ObjectAttachment Table Edit\" viewdef=\"ObjectAttachment Table\" mode=\"edit\" /\u003E\n\t\t\t\t\u003Caltview name=\"ObjectAttachment Form View\" viewdef=\"ObjectAttachment Form\" label=\"Form\" mode=\"view\" default=\"true\" /\u003E\n\t\t\t\t\u003Caltview name=\"ObjectAttachment Form Edit\" viewdef=\"ObjectAttachment Form\" label=\"Form\" mode=\"edit\" /\u003E\n\t\t\t\u003C/altviews\u003E\n\t\t\u003C/view\u003E\n\t\t", + "viewsetName": "Global", + "viewsetLevel": "Backstop", + "viewsetSource": "disk", + "viewsetId": null, + "viewsetFile": "backstop/global.views.xml" + }); + + overrideAjax("/context/view.json?name=CollectionObjectAttachment&quiet=", { + "name": "CollectionObjectAttachment", + "class": "edu.ku.brc.specify.datamodel.CollectionObjectAttachment", + "busrules": "edu.ku.brc.specify.datamodel.busrules.AttachmentBusRules", + "resourcelabels": "false", + "altviews": { + "CollectionObjectAttachment Icon View": { + "name": "CollectionObjectAttachment Icon View", + "viewdef": "CollectionObjectAttachment IconView", + "mode": "view" + }, + "CollectionObjectAttachment Icon Edit": { + "name": "CollectionObjectAttachment Icon Edit", + "viewdef": "CollectionObjectAttachment IconView", + "mode": "edit" + }, + "CollectionObjectAttachment Table View": { + "name": "CollectionObjectAttachment Table View", + "viewdef": "CollectionObjectAttachment Table", + "mode": "view" + }, + "CollectionObjectAttachment Table Edit": { + "name": "CollectionObjectAttachment Table Edit", + "viewdef": "CollectionObjectAttachment Table", + "mode": "edit" + }, + "CollectionObjectAttachment Form View": { + "name": "CollectionObjectAttachment Form View", + "viewdef": "CollectionObjectAttachment Form", + "label": "Form", + "mode": "view", + "default": "true" + }, + "CollectionObjectAttachment Form Edit": { + "name": "CollectionObjectAttachment Form Edit", + "viewdef": "CollectionObjectAttachment Form", + "label": "Form", + "mode": "edit" + } + }, + "viewdefs": { + "CollectionObjectAttachment Form": "\u003Cviewdef type=\"form\" name=\"CollectionObjectAttachment Form\" class=\"edu.ku.brc.specify.datamodel.CollectionObjectAttachment\" gettable=\"edu.ku.brc.af.ui.forms.DataGetterForObj\" settable=\"edu.ku.brc.af.ui.forms.DataSetterForObj\" useresourcelabels=\"true\"\u003E\n\t\t\t\u003Cdesc\u003EThe CollectionObjectAttachment form.\u003C/desc\u003E\n\t\t\t\u003C!--\u003CcolumnDef\u003E110px,2dlu,p:g,5dlu,100px,2dlu,85px\u003C/columnDef\u003E --\u003E\n\t\t\t\u003CcolumnDef\u003Ep,5dlu,p:g\u003C/columnDef\u003E\n\t\t\t\u003CrowDef auto=\"true\" cell=\"p\" sep=\"2px\" /\u003E\n\t\t\t\u003Crows\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"origFilename\" label=\"FILENAME\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"origFilename\" name=\"attachment.origFilename\" initialize=\"editoncreate=true\" uitype=\"browse\" isrequired=\"true\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"title\" label=\"TITLE\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"title\" name=\"attachment.title\" isrequired=\"true\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003C!--\u003Crow\u003E\n \u003Ccell type=\"subview\" id=\"metadata\" name=\"attachment.metadata\" viewname=\"AttachmentMetadata\" colspan=\"3\"/\u003E\n \u003C/row\u003E --\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"dateImaged\" label=\"DATE_IMAGED\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"dateImaged\" name=\"attachment.dateImaged\" uitype=\"text\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"fileCreatedDate\" label=\"FILE_CREATED_DATE\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"fileCreatedDate\" name=\"attachment.fileCreatedDate\" uitype=\"formattedtext\" uifieldformatter=\"Date\" default=\"today\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"copyrightHolder\" label=\"COPYRIGHT_HOLDER\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"copyrightHolder\" name=\"attachment.copyrightHolder\" uitype=\"text\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"copyrightDate\" label=\"COPYRIGHT_DATE\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"copyrightDate\" name=\"attachment.copyrightDate\" uitype=\"text\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"attachmentLocation\" label=\"ATTACHMENT_LOCATION\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"attachmentLocation\" name=\"attachment.attachmentLocation\" uitype=\"text\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"isPublic\" label=\"is public\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"isPublic\" name=\"attachment.isPublic\" isrequired=\"true\" uitype=\"checkbox\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"license\" label=\"LICENSE\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"license\" name=\"attachment.license\" uitype=\"text\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"credit\" label=\"CREDIT\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"credit\" name=\"attachment.credit\" uitype=\"text\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"mimeType\" label=\"MIME_TYPE\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"mimeType\" name=\"attachment.mimeType\" uitype=\"text\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"remarks\" label=\"REMARKS\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"remarks\" name=\"attachment.remarks\" uitype=\"textareabrief\" rows=\"2\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003C!--\u003Crow\u003E\n \u003Ccell type=\"subview\" id=\"imageatt\" viewname=\"AttachmentImageAttribute\" name=\"attachment.attachmentImageAttribute\" colspan=\"3\" /\u003E\n \u003C/row\u003E--\u003E\n\t\t\t\u003C/rows\u003E\n\t\t\u003C/viewdef\u003E\n\t\t", + "CollectionObjectAttachment Table": "\u003Cviewdef type=\"formtable\" name=\"CollectionObjectAttachment Table\" class=\"edu.ku.brc.specify.datamodel.CollectionObjectAttachment\" gettable=\"edu.ku.brc.af.ui.forms.DataGetterForObj\" settable=\"edu.ku.brc.af.ui.forms.DataSetterForObj\"\u003E\n\t\t\t\u003Cdesc\u003EObjectAttachment grid view.\u003C/desc\u003E\n\t\t\t\u003Cdefinition\u003EObjectAttachment Form\u003C/definition\u003E\n\t\t\u003C/viewdef\u003E\n\t\t", + "CollectionObjectAttachment IconView": "\u003Cviewdef type=\"iconview\" name=\"CollectionObjectAttachment IconView\" class=\"edu.ku.brc.specify.datamodel.CollectionObjectAttachment\" gettable=\"edu.ku.brc.af.ui.forms.DataGetterForObj\" settable=\"edu.ku.brc.af.ui.forms.DataSetterForObj\"\u003E\n\t\t\t\u003Cdesc\u003EThe ObjectAttachment Icon Viewer\u003C/desc\u003E\n\t\t\u003C/viewdef\u003E\n\t\t", + "ObjectAttachment Form": "\u003Cviewdef type=\"form\" name=\"ObjectAttachment Form\" class=\"edu.ku.brc.specify.datamodel.ObjectAttachmentIFace\" gettable=\"edu.ku.brc.af.ui.forms.DataGetterForObj\" settable=\"edu.ku.brc.af.ui.forms.DataSetterForObj\" useresourcelabels=\"true\"\u003E\n\t\t\t\u003Cdesc\u003EThe ObjectAttachment form.\u003C/desc\u003E\n\t\t\t\u003CcolumnDef\u003Ep,2px,p:g\u003C/columnDef\u003E\n\t\t\t\u003CrowDef auto=\"true\" cell=\"p\" sep=\"2px\" /\u003E\n\t\t\t\u003Crows\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"origFilename\" label=\"FILENAME\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"origFilename\" name=\"attachment.origFilename\" initialize=\"editoncreate=true\" uitype=\"browse\" isrequired=\"true\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"title\" label=\"TITLE\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"title\" name=\"attachment.title\" uitype=\"text\" isrequired=\"true\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003Crow\u003E\n\t\t\t\t\t\u003Ccell type=\"label\" labelfor=\"ispub\" label=\"\" /\u003E\n\t\t\t\t\t\u003Ccell type=\"field\" id=\"ispub\" name=\"attachment.isPublic\" uitype=\"checkbox\" label=\"Make Public\" /\u003E\n\t\t\t\t\u003C/row\u003E\n\t\t\t\t\u003C!-- \u003Crow\u003E\n \t\t \u003Ccell type=\"subview\" id=\"metadata\" name=\"attachment.metadata\" viewname=\"AttachmentMetadata\" colspan=\"3\"/\u003E\n \t\t \u003C/row\u003E --\u003E\n\t\t\t\u003C/rows\u003E\n\t\t\u003C/viewdef\u003E\n\t\t" + }, + "view": "\u003Cview name=\"CollectionObjectAttachment\" class=\"edu.ku.brc.specify.datamodel.CollectionObjectAttachment\" busrules=\"edu.ku.brc.specify.datamodel.busrules.AttachmentBusRules\" resourcelabels=\"false\"\u003E\n\t\t\t\u003Cdesc\u003EThe Collection Object-Attachment View.\u003C/desc\u003E\n\t\t\t\u003Caltviews\u003E\n\t\t\t\t\u003Caltview name=\"CollectionObjectAttachment Icon View\" viewdef=\"CollectionObjectAttachment IconView\" mode=\"view\" /\u003E\n\t\t\t\t\u003Caltview name=\"CollectionObjectAttachment Icon Edit\" viewdef=\"CollectionObjectAttachment IconView\" mode=\"edit\" /\u003E\n\t\t\t\t\u003Caltview name=\"CollectionObjectAttachment Table View\" viewdef=\"CollectionObjectAttachment Table\" mode=\"view\" /\u003E\n\t\t\t\t\u003Caltview name=\"CollectionObjectAttachment Table Edit\" viewdef=\"CollectionObjectAttachment Table\" mode=\"edit\" /\u003E\n\t\t\t\t\u003Caltview name=\"CollectionObjectAttachment Form View\" viewdef=\"CollectionObjectAttachment Form\" label=\"Form\" mode=\"view\" default=\"true\" /\u003E\n\t\t\t\t\u003Caltview name=\"CollectionObjectAttachment Form Edit\" viewdef=\"CollectionObjectAttachment Form\" label=\"Form\" mode=\"edit\" /\u003E\n\t\t\t\u003C/altviews\u003E\n\t\t\u003C/view\u003E\n\t\t", + "viewsetName": "Global", + "viewsetLevel": "Backstop", + "viewsetSource": "disk", + "viewsetId": null, + "viewsetFile": "backstop/global.views.xml" + }); + + test("simple render", async () => { + jest.spyOn(console, 'warn').mockImplementation(); + + overrideAttachmentSettings(attachmentSettings); + + const setRelated = jest.fn(); + const handleClose = jest.fn(); + const handleChange = jest.fn(); + const handlePrevious = jest.fn(); + const handleNext = jest.fn(); + const handleViewRecord = jest.fn(); + + const related = deserializeResource(testCollectionObjectAttachment as unknown as SerializedResource); + + const { getByRole, rerender } = mount( + + + + + + ); + + const dialog = getByRole('dialog'); + await waitFor(() => { + expect(setStateMock).toBeCalled(); + }); + expect(dialog).toMatchSnapshot(); + + }); + +}); \ No newline at end of file diff --git a/specifyweb/frontend/js_src/lib/components/Attachments/__tests__/__snapshots__/AttachmentDialog.test.tsx.snap b/specifyweb/frontend/js_src/lib/components/Attachments/__tests__/__snapshots__/AttachmentDialog.test.tsx.snap new file mode 100644 index 00000000000..026725c9509 --- /dev/null +++ b/specifyweb/frontend/js_src/lib/components/Attachments/__tests__/__snapshots__/AttachmentDialog.test.tsx.snap @@ -0,0 +1,433 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AttachmentDialog simple render 1`] = ` + +`; diff --git a/specifyweb/frontend/js_src/lib/components/Attachments/__tests__/utils.ts b/specifyweb/frontend/js_src/lib/components/Attachments/__tests__/utils.ts new file mode 100644 index 00000000000..dc06527424b --- /dev/null +++ b/specifyweb/frontend/js_src/lib/components/Attachments/__tests__/utils.ts @@ -0,0 +1,129 @@ +import React from "react"; +import { GetOrSet, RA } from "../../../utils/types"; +import { resourceOn } from "../../DataModel/resource"; +import { act } from "react-dom/test-utils"; + +const testAttachment = { + "id": 5490, + "attachmentLocation": "a6daad6c-5091-4461-86ee-eeba9434ef2b.jpg", + "attachmentStorageConfig": null, + "captureDevice": null, + "copyrightDate": null, + "copyrightHolder": null, + "credit": null, + "dateImaged": null, + "fileCreatedDate": null, + "guid": "7664fc1b-cc39-4f7d-b81c-645fc11e8e1d", + "isPublic": true, + "license": null, + "licenseLogoUrl": null, + "metadataText": null, + "mimeType": "image/jpeg", + "origFilename": "topbrc4.jpg", + "remarks": null, + "scopeID": null, + "scopeType": null, + "subjectOrientation": null, + "subtype": null, + "tableID": 1, + "timestampCreated": "2015-05-20T12:16:38", + "timestampModified": "2015-05-20T12:16:38", + "title": "kubiodiversitylogo.jpg", + "type": null, + "version": 0, + "visibility": null, + "attachmentImageAttribute": null, + "createdByAgent": "/api/specify/agent/3/", + "creator": null, + "modifiedByAgent": null, + "visibilitySetBy": null, + "accessionAttachments": "/api/specify/accessionattachment/?attachment=5490", + "agentAttachments": "/api/specify/agentattachment/?attachment=5490", + "metadata": "/api/specify/attachmentmetadata/?attachment=5490", + "tags": "/api/specify/attachmenttag/?attachment=5490", + "borrowAttachments": "/api/specify/borrowattachment/?attachment=5490", + "collectingEventAttachments": "/api/specify/collectingeventattachment/?attachment=5490", + "collectingTripAttachments": "/api/specify/collectingtripattachment/?attachment=5490", + "collectionObjectAttachments": "/api/specify/collectionobjectattachment/?attachment=5490", + "conservDescriptionAttachments": "/api/specify/conservdescriptionattachment/?attachment=5490", + "conservEventAttachments": "/api/specify/conserveventattachment/?attachment=5490", + "dnaSequenceAttachments": "/api/specify/dnasequenceattachment/?attachment=5490", + "dnaSequencingRunAttachments": "/api/specify/dnasequencingrunattachment/?attachment=5490", + "deaccessionAttachments": "/api/specify/deaccessionattachment/?attachment=5490", + "disposalAttachments": "/api/specify/disposalattachment/?attachment=5490", + "exchangeInAttachments": "/api/specify/exchangeinattachment/?attachment=5490", + "exchangeOutAttachments": "/api/specify/exchangeoutattachment/?attachment=5490", + "fieldNotebookAttachments": "/api/specify/fieldnotebookattachment/?attachment=5490", + "fieldNotebookPageAttachments": "/api/specify/fieldnotebookpageattachment/?attachment=5490", + "fieldNotebookPageSetAttachments": "/api/specify/fieldnotebookpagesetattachment/?attachment=5490", + "giftAttachments": "/api/specify/giftattachment/?attachment=5490", + "loanAttachments": "/api/specify/loanattachment/?attachment=5490", + "localityAttachments": "/api/specify/localityattachment/?attachment=5490", + "permitAttachments": "/api/specify/permitattachment/?attachment=5490", + "preparationAttachments": "/api/specify/preparationattachment/?attachment=5490", + "referenceWorkAttachments": "/api/specify/referenceworkattachment/?attachment=5490", + "repositoryAgreementAttachments": "/api/specify/repositoryagreementattachment/?attachment=5490", + "storageAttachments": "/api/specify/storageattachment/?attachment=5490", + "taxonAttachments": "/api/specify/taxonattachment/?attachment=5490", + "treatmentEventAttachments": "/api/specify/treatmenteventattachment/?attachment=5490", + "spDataSetAttachments": "/api/specify/spdatasetattachment/?attachment=5490", + "resource_uri": "/api/specify/attachment/5490/", + "_tableName": "Attachment" +} as const; + +const testCollectionObjectAttachment = { + "id": 4732, + "collectionmemberid": 4, + "ordinal": 0, + "remarks": null, + "timestampcreated": "2025-07-31T17:00:40", + "timestampmodified": "2025-07-31T17:00:40", + "version": 0, + attachment: testAttachment, + "collectionobject": "/api/specify/collectionobject/51731/", + "createdbyagent": "/api/specify/agent/3/", + "modifiedbyagent": null, + "resource_uri": "/api/specify/collectionobjectattachment/4732/", + "_tableName": "CollectionObjectAttachment" +} as const; + +export const testStaticResources = { + testAttachment, + testCollectionObjectAttachment +}; + +export function makeUseAsyncRef(onSetState: (data: T | undefined)=>void){ + + function useAsyncRef( + callback: () => Promise | INNER | undefined, + _: boolean + ): GetOrSet { + const ref = React.useRef(undefined); + + React.useLayoutEffect(() => { + ref.current = undefined; + + Promise.resolve(callback()).then((data) => { + if (destructorCalled) return; + ref.current = data; + onSetState(data); + }); + + let destructorCalled = false; + + return () => { + destructorCalled = true; + } + }, [callback]); + + const setCallback = (...value: Parameters[1]>) => { + const setter = value[0]; + ref.current = typeof setter === 'function' ? (setter as Function)(ref.current) : setter; + } + + return [ref.current, setCallback]; + } + + return useAsyncRef + +}