From ab97fee8a91f5760867dea0a7cec624181abf7f0 Mon Sep 17 00:00:00 2001 From: kwhuber Date: Fri, 4 Sep 2026 14:16:10 -0500 Subject: [PATCH 1/9] [test]: rough sketch of unit test --- .../WbUtils/__tests__/datasetVariants.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts diff --git a/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts b/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts new file mode 100644 index 00000000000..f6138d54fdb --- /dev/null +++ b/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts @@ -0,0 +1,10 @@ +import { hasPermission } from '../../Permissions/helpers'; +import { userPreferences } from '../../Preferences/userPreferences'; +import { datasetVariants } from '../datasetVariants'; + +jest.mock('../../Permissions/helpers', () => ({ + hasPermission: jest.fn(), +})); + +const mockedHasPermission = hasPermission as jest.Mock; + From f31587bf76d2a6f3cc460c9122a85b1f5480161e Mon Sep 17 00:00:00 2001 From: "Caroline D." <108160931+CarolineDenis@users.noreply.github.com> Date: Mon, 7 Sep 2026 08:29:23 +0200 Subject: [PATCH 2/9] Potential fix for pull request finding 'CodeQL / Unused variable, import, function or class' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .../lib/components/WbUtils/__tests__/datasetVariants.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts b/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts index f6138d54fdb..a11a8efec53 100644 --- a/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts +++ b/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts @@ -1,5 +1,4 @@ import { hasPermission } from '../../Permissions/helpers'; -import { userPreferences } from '../../Preferences/userPreferences'; import { datasetVariants } from '../datasetVariants'; jest.mock('../../Permissions/helpers', () => ({ From 57fb8d5148ecf36f86c8906947fc20e83b6ebb3f Mon Sep 17 00:00:00 2001 From: "Caroline D." <108160931+CarolineDenis@users.noreply.github.com> Date: Mon, 7 Sep 2026 08:30:34 +0200 Subject: [PATCH 3/9] Potential fix for pull request finding 'CodeQL / Unused variable, import, function or class' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .../lib/components/WbUtils/__tests__/datasetVariants.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts b/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts index a11a8efec53..cc8720b9213 100644 --- a/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts +++ b/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts @@ -5,5 +5,3 @@ jest.mock('../../Permissions/helpers', () => ({ hasPermission: jest.fn(), })); -const mockedHasPermission = hasPermission as jest.Mock; - From d6188dc51f0397be17d9564b6d8e86dd4503d77d Mon Sep 17 00:00:00 2001 From: kwhuber Date: Tue, 8 Sep 2026 15:15:23 -0500 Subject: [PATCH 4/9] [test]: WIP of disable relationships for batch edit and enable rollback in user preferences --- .../WbUtils/__tests__/datasetVariants.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts b/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts index f6138d54fdb..426477af43f 100644 --- a/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts +++ b/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts @@ -8,3 +8,15 @@ jest.mock('../../Permissions/helpers', () => ({ const mockedHasPermission = hasPermission as jest.Mock; +const setBatchEditPreferences = ( + enableRelationships: boolean, + showRollback: boolean +): void => { + userPreferences.set( + 'batchEdit', + 'editor', + 'enableRelationships', + enableRelationships + ); + userPreferences.set('batchEdit', 'editor', 'showRollback', showRollback); +}; \ No newline at end of file From 341e3e54a4e8472e658cbe2c855846abf5bce7ad Mon Sep 17 00:00:00 2001 From: kwhuber Date: Tue, 8 Sep 2026 16:19:15 -0500 Subject: [PATCH 5/9] test commit --- .../lib/components/WbUtils/__tests__/datasetVariants.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts b/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts index 426477af43f..0cca996878c 100644 --- a/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts +++ b/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts @@ -1,3 +1,4 @@ +// test import { hasPermission } from '../../Permissions/helpers'; import { userPreferences } from '../../Preferences/userPreferences'; import { datasetVariants } from '../datasetVariants'; @@ -19,4 +20,4 @@ const setBatchEditPreferences = ( enableRelationships ); userPreferences.set('batchEdit', 'editor', 'showRollback', showRollback); -}; \ No newline at end of file +}; From 0cc0e4569a6a031bd39d5cb5b020a3a633fc9ac6 Mon Sep 17 00:00:00 2001 From: kwhuber Date: Thu, 10 Sep 2026 17:13:42 -0500 Subject: [PATCH 6/9] more WIP of disable relationships for batch edit and enable rollback in user preferences --- .../WbUtils/__tests__/datasetVariants.test.ts | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts b/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts index 0cca996878c..79d9fd7f0a8 100644 --- a/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts +++ b/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts @@ -1,4 +1,3 @@ -// test import { hasPermission } from '../../Permissions/helpers'; import { userPreferences } from '../../Preferences/userPreferences'; import { datasetVariants } from '../datasetVariants'; @@ -21,3 +20,43 @@ const setBatchEditPreferences = ( ); userPreferences.set('batchEdit', 'editor', 'showRollback', showRollback); }; + +beforeAll(() => { + jest.useFakeTimers(); +}); + +afterAll(() => { + jest.useRealTimers(); +}); + +describe('batch edit rollback availability', () => { + test.each([ + [false, true, true, true], + [true, true, true, false], + [false, false, true, false], + [false, true, false, false], + [true, false, true, false], + [true, true, false, false], + [false, false, false, false], + [true, false, false, false], + ])( + 'enableRelationships=%s showRollback=%s permission=%s -> canUndo=%s', + (enableRelationships, showRollback, permission, expected) => { + setBatchEditPreferences(enableRelationships, showRollback); + mockedHasPermission.mockReturnValue(permission); + + expect(datasetVariants.batchEdit.canUndo()).toBe(expected); + } + ); + + test('checks the batch edit rollback permission', () => { + setBatchEditPreferences(false, true); + mockedHasPermission.mockReturnValue(true); + + datasetVariants.batchEdit.canUndo(); + + expect(mockedHasPermission).toHaveBeenCalledWith( + '/batch_edit/dataset', + 'rollback' + ); + }); \ No newline at end of file From 1c3de4dfa757f24be0f765479805275384772db7 Mon Sep 17 00:00:00 2001 From: kwhuber Date: Fri, 11 Sep 2026 09:47:24 -0500 Subject: [PATCH 7/9] [test]: disable relationships for batch edit and enable rollback in user preferences --- .../WbUtils/__tests__/datasetVariants.test.ts | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts b/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts index 79d9fd7f0a8..8e7af4fd268 100644 --- a/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts +++ b/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts @@ -59,4 +59,40 @@ describe('batch edit rollback availability', () => { '/batch_edit/dataset', 'rollback' ); - }); \ No newline at end of file + }); + + test('does not consult permissions when relationships are enabled', () => { + setBatchEditPreferences(true, true); + mockedHasPermission.mockReturnValue(true); + + expect(datasetVariants.batchEdit.canUndo()).toBe(false); + expect(mockedHasPermission).not.toHaveBeenCalled(); + }); + + test('rollback is hidden based off default preferences', () => { + expect( + userPreferences.definition('batchEdit', 'editor', 'enableRelationships') + .defaultValue + ).toBe(true); + expect( + userPreferences.definition('batchEdit', 'editor', 'showRollback') + .defaultValue + ).toBe(true); + + setBatchEditPreferences(true, true); + mockedHasPermission.mockReturnValue(true); + + expect(datasetVariants.batchEdit.canUndo()).toBe(false); + }); + + test('workbench rollback is not affected by batch edit preferences', () => { + setBatchEditPreferences(true, true); + mockedHasPermission.mockReturnValue(true); + + expect(datasetVariants.workbench.canUndo()).toBe(true); + expect(mockedHasPermission).toHaveBeenCalledWith( + '/workbench/dataset', + 'unupload' + ); + }); +}); \ No newline at end of file From 954e48058d664ca6dcfbcc9a96ebffab84c4f8b6 Mon Sep 17 00:00:00 2001 From: kwhuber Date: Fri, 11 Sep 2026 10:16:48 -0500 Subject: [PATCH 8/9] [fix]: small change for BE relationships and rollback permissions --- .../WbUtils/__tests__/datasetVariants.test.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts b/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts index 8e7af4fd268..8a52e434f7f 100644 --- a/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts +++ b/specifyweb/frontend/js_src/lib/components/WbUtils/__tests__/datasetVariants.test.ts @@ -21,6 +21,9 @@ const setBatchEditPreferences = ( userPreferences.set('batchEdit', 'editor', 'showRollback', showRollback); }; +// Drop every explicitly set preference so lookups fall back to the defaults +const resetPreferences = (): void => userPreferences.setRaw({}); + beforeAll(() => { jest.useFakeTimers(); }); @@ -70,6 +73,8 @@ describe('batch edit rollback availability', () => { }); test('rollback is hidden based off default preferences', () => { + resetPreferences(); + expect( userPreferences.definition('batchEdit', 'editor', 'enableRelationships') .defaultValue @@ -79,10 +84,16 @@ describe('batch edit rollback availability', () => { .defaultValue ).toBe(true); - setBatchEditPreferences(true, true); - mockedHasPermission.mockReturnValue(true); + // Unset preferences resolve to the declared defaults + expect( + userPreferences.get('batchEdit', 'editor', 'enableRelationships') + ).toBe(true); + expect(userPreferences.get('batchEdit', 'editor', 'showRollback')).toBe( + true + ); expect(datasetVariants.batchEdit.canUndo()).toBe(false); + expect(mockedHasPermission).not.toHaveBeenCalled(); }); test('workbench rollback is not affected by batch edit preferences', () => { From 320ae37e9f2dc5034474394a534bd614f1ecf745 Mon Sep 17 00:00:00 2001 From: kwhuber Date: Fri, 11 Sep 2026 12:27:59 -0500 Subject: [PATCH 9/9] [test]: WIP of create a query; very relationsips are not editable --- .../BatchEdit/__tests__/index.test.ts | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 specifyweb/frontend/js_src/lib/components/BatchEdit/__tests__/index.test.ts diff --git a/specifyweb/frontend/js_src/lib/components/BatchEdit/__tests__/index.test.ts b/specifyweb/frontend/js_src/lib/components/BatchEdit/__tests__/index.test.ts new file mode 100644 index 00000000000..21f43c77a05 --- /dev/null +++ b/specifyweb/frontend/js_src/lib/components/BatchEdit/__tests__/index.test.ts @@ -0,0 +1,73 @@ +import { requireContext } from '../../../tests/helpers'; +import type { RA } from '../../../utils/types'; +import { tables } from '../../DataModel/tables'; +import type { QueryField } from '../../QueryBuilder/helpers'; +import type { MappingPath } from '../../WbPlanView/Mapper'; +import { buildBatchEditFromQueryBody } from '../index'; + +requireContext(); + +const queryField = ( + mappingPath: MappingPath, + isDisplay: boolean = true +): QueryField => ({ + id: 0, + mappingPath, + sortType: undefined, + isDisplay, + filters: [], +}); + +/* A query over Collection Object that reaches through a to-one relationship + * (accession), a to-many one (accession agents), and an unchecked field. + */ +const fields: RA = [ + queryField(['catalogNumber']), + queryField(['guid'], false), // Unchecked in the query builder + queryField(['accession', 'accessionNumber']), + queryField(['accession', 'accessionAgents', '#1', 'role']), +]; + +const buildBody = ( + hasRelationships: boolean, + extraFields: RA = fields +) => + buildBatchEditFromQueryBody({ + query: new tables.SpQuery.Resource({ name: 'Test Query' }), + limit: 5000, + fields: extraFields, + baseTableName: 'CollectionObject', + dataSetName: 'Test Query - Fri Sep 11 2026', + recordSetId: 42, + treeDefsFilter: {}, + hasRelationships, + }); + +describe('buildBatchEditFromQueryBody', () => { + test('captions describe each displayed field with relationships', () => { + expect(buildBody(true).captions).toEqual([ + 'Cat #', + 'Accession #', + 'Accession Agents - Role', + ]); + }); + + test('fields hidden in the query builder get no caption', () => { + const captions = buildBody(true).captions; + expect(captions).toHaveLength(3); + expect(captions).not.toContain('Collection Object - GUID'); + }); + + // Covers front end half of verifying relationships are not editable. + test('omitRelationships is the inverse of the preference', () => { + expect(buildBody(true).omitrelationships).toBe(false); + expect(buildBody(false).omitrelationships).toBe(true); + }); + + test('carries the data set name, limit and record set through', () => { + const body = buildBody(true); + expect(body.name).toBe('Test Query - Fri Sep 11 2026'); + expect(body.limit).toBe(5000); + expect(body.recordsetid).toBe(42); + }); +});