diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/resourceApi.test.ts b/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/resourceApi.test.ts index 9b4fda0164d..1442e62f761 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/resourceApi.test.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/resourceApi.test.ts @@ -469,6 +469,120 @@ test('save', async () => { expect(newDetermination.get('number1')).toBe(2); }); +describe('independent resource change propagation', () => { + overrideAjax( + '/api/specify/collectionobject/?domainfilter=false&accession=11&offset=0', + emptyCollection + ); + overrideAjax(accessionUrl, accessionResponse, { method: 'PUT' }); + + test('needsSaved propagates from independent collection to parent', async () => { + const parentResource = new tables.Accession.Resource({ id: accessionId }); + expect(parentResource.needsSaved).toBe(false); + + const collectionObjectRel = + tables.CollectionObject.strictGetRelationship('accession')!; + + const independentCollection = + new tables.CollectionObject.IndependentCollection({ + related: parentResource, + field: collectionObjectRel, + }) as Collection; + + await independentCollection.fetch(); + + // Connect the collection to the parent's event system, as rgetCollection would do internally. + // storeIndependent is not in the public TS types so cast to any. + (parentResource as any).storeIndependent( + collectionObjectRel.getReverse(), + independentCollection + ); + + const newCollectionObject = new tables.CollectionObject.Resource({ + id: 998, + }); + independentCollection.add(newCollectionObject); + + // Adding an existing resource to an independent collection does not mark the resource itself needsSaved; + // only the parent is notified via saverequired + expect(newCollectionObject.needsSaved).toBe(false); + expect(parentResource.needsSaved).toBe(true); + + await parentResource.save(); + + expect(parentResource.needsSaved).toBe(false); + }); +}); + +describe('save base record which has independent subviews', () => { + overrideAjax( + '/api/specify/collectionobject/?domainfilter=false&accession=11&offset=0', + { + objects: [collectionObjectResponse], + meta: { limit: 20, offset: 0, total_count: 1 }, + } + ); + overrideAjax(accessionUrl, accessionResponse, { method: 'PUT' }); + + async function setupParentWithIndependentCollection() { + const parentResource = new tables.Accession.Resource({ id: accessionId }); + const collectionObjectRel = + tables.CollectionObject.strictGetRelationship('accession')!; + const independentCollection = + new tables.CollectionObject.IndependentCollection({ + //creates a new form with + related: parentResource, + field: collectionObjectRel, + }) as Collection; + await independentCollection.fetch(); + (parentResource as any).storeIndependent( + collectionObjectRel.getReverse(), + independentCollection + ); + return { parentResource, independentCollection }; + } + + test('modifying a field on an independent resource marks base record as needsSaved and save applies the change', async () => { + const { parentResource, independentCollection } = + await setupParentWithIndependentCollection(); //creates fake parent form and fake list of independent subviews + + expect(parentResource.needsSaved).toBe(false); //since we just created a new record, the needsSaved needs to be false + + const existingCollectionObject = independentCollection.models[0]; //get the first collection object from collection + existingCollectionObject.set('text1', 'changed-value'); //change a field in the collection object + + expect(parentResource.needsSaved).toBe(true); //after you change, the save button should light up + + await parentResource.save(); //save + + expect(parentResource.needsSaved).toBe(false); //after saving, save button should be disabled + // Change is preserved in memory after save + expect(existingCollectionObject.get('text1')).toBe('changed-value'); //the change we made is still in memory + }); + + test('modifying a sub-record of an independent resource propagates needsSaved to base record and save completes', async () => { + const { parentResource, independentCollection } = + await setupParentWithIndependentCollection(); + + expect(parentResource.needsSaved).toBe(false); + + const collectionObject = independentCollection.models[0]; + const determinations = + collectionObject.getDependentResource('determinations'); + const determination = determinations!.models[0]; + determination.set('number1', 99); + + // Change to sub-record propagates needsSaved all the way up to base record + expect(parentResource.needsSaved).toBe(true); + + await parentResource.save(); + + expect(parentResource.needsSaved).toBe(false); + // Change to sub-record is preserved in memory after save + expect(determination.get('number1')).toBe(99); + }); +}); + describe('resource initialization', () => { test('Initialization with dependent resources does not trigger saveRequired', () => { const resource = new tables.CollectionObject.Resource({