From ee53686a0d626069ad1a4006a1faef8d6b458b26 Mon Sep 17 00:00:00 2001 From: John Camden Date: Tue, 28 Oct 2025 15:03:28 -0400 Subject: [PATCH 1/7] fix(metadata): restore taxonomy field filter in preview --- package.json | 2 +- src/api/Metadata.js | 10 +++++----- src/api/__tests__/Metadata.test.js | 6 +++--- .../stories/__mocks__/TaxonomyMocks.ts | 2 +- yarn.lock | 18 +++++++++--------- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index bbd750645e..e26ff0f53b 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "@box/box-ai-content-answers": "^0.139.0", "@box/box-item-type-selector": "^0.73.1", "@box/cldr-data": "^34.2.0", - "@box/combobox-with-api": "^0.34.9", + "@box/combobox-with-api": "^1.3.11", "@box/copy-input": "^1.5.3", "@box/frontend": "^11.0.1", "@box/item-icon": "^0.27.1", diff --git a/src/api/Metadata.js b/src/api/Metadata.js index ddc567aaa4..b9ed9a1fc7 100644 --- a/src/api/Metadata.js +++ b/src/api/Metadata.js @@ -1348,19 +1348,19 @@ class Metadata extends File { const { marker, - searchInput: query_text, + searchInput: query, onlySelectableOptions, - ancestorId: ancestor_id, + ancestorId: ancestor, level: optionsLevel, signal, } = options; const params: {} = { ...(marker ? { marker } : {}), - ...(query_text ? { query_text } : {}), + ...(query ? { query } : {}), ...(optionsLevel ? { level: optionsLevel } : {}), - ...(ancestor_id ? { ancestor_id } : {}), - ...(onlySelectableOptions !== undefined ? { only_selectable_options: !!onlySelectableOptions } : {}), + ...(ancestor ? { ancestor } : {}), + ...(onlySelectableOptions !== undefined ? { 'only-selectable-options': !!onlySelectableOptions } : {}), limit: 100, }; diff --git a/src/api/__tests__/Metadata.test.js b/src/api/__tests__/Metadata.test.js index 00c17e5c58..fc9428cac3 100644 --- a/src/api/__tests__/Metadata.test.js +++ b/src/api/__tests__/Metadata.test.js @@ -3184,12 +3184,12 @@ describe('api/Metadata', () => { url: 'options_url', id: 'file_id', params: { - ancestor_id: '123', + ancestor: '123', level: 1, limit: 100, marker: 'current_marker', - only_selectable_options: false, - query_text: 'search_term', + 'only-selectable-options': false, + query: 'search_term', }, }); }); diff --git a/src/elements/content-sidebar/stories/__mocks__/TaxonomyMocks.ts b/src/elements/content-sidebar/stories/__mocks__/TaxonomyMocks.ts index 5ee9532ae1..899d5fd3ba 100644 --- a/src/elements/content-sidebar/stories/__mocks__/TaxonomyMocks.ts +++ b/src/elements/content-sidebar/stories/__mocks__/TaxonomyMocks.ts @@ -417,7 +417,7 @@ export const taxonomyMockHandlers: HttpHandler[] = [ }), http.get(mockMultilevelTaxonomyOptions.url, ({ request }) => { const url = new URL(request.url); - const ancestorId = url.searchParams.get('ancestor_id'); + const ancestorId = url.searchParams.get('ancestor'); if (!ancestorId) { return HttpResponse.json(mockMultilevelTaxonomyOptions.response.firstLevel); diff --git a/yarn.lock b/yarn.lock index e147af1ef3..ad21449f6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1480,12 +1480,12 @@ resolved "https://registry.yarnpkg.com/@box/cldr-data/-/cldr-data-34.8.0.tgz#36e6ddcea8e20653326aba2e0d13e07f34b7704f" integrity sha512-jsTnhhpFy/eMossMr3cP9+1VFqOxOzO1GX/csw0LzasPl0Dg2Jhn8ypeNTBnFwlOB2Dp6XoxdvQHkBG4eVQe/Q== -"@box/combobox-with-api@^0.34.9": - version "0.34.9" - resolved "https://registry.yarnpkg.com/@box/combobox-with-api/-/combobox-with-api-0.34.9.tgz#0620ac1ef80719a3c6d95f27f731ec92e03bb05c" - integrity sha512-jE257A/6rlN5GydNWh+T+K4w8xmoaDaPb+J6MOh8pz4AkTJuZXOSp9jhfHP7KRwrQs849moBDpWowF0xOnQ2+g== +"@box/combobox-with-api@^1.3.11": + version "1.3.13" + resolved "https://registry.yarnpkg.com/@box/combobox-with-api/-/combobox-with-api-1.3.13.tgz#bfe008b1fac3ac59a2459a98b1e7d9f8783799b0" + integrity sha512-PN+unnjkbiJf55JTKhz2lYIxCI28IX7b+ds+YUsStcrER9IrQuX7sbdA7UwJ+yW/uJBXF5zpyd4tnDIAloaSIQ== dependencies: - "@box/tree" "^0.45.4" + "@box/tree" "^1.2.6" react-accessible-treeview "2.9.0" "@box/copy-input@^1.5.3": @@ -1539,10 +1539,10 @@ prop-types "^15.7.2" react-lifecycles-compat "^3.0.4" -"@box/tree@^0.45.4": - version "0.45.4" - resolved "https://registry.yarnpkg.com/@box/tree/-/tree-0.45.4.tgz#605051e77e64ebb3c03ceefc9ee99f0a0d2ed4ae" - integrity sha512-gJLYz4bF+xci7o0e8oM2qAtc0lSRQYGrlOYMtZO5QvoLY0JjtAHtNaI9axmL7leJsy7VuCCxFoSFUoSXMHVRMA== +"@box/tree@^1.2.6": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@box/tree/-/tree-1.2.6.tgz#998611a8e23f94959a20e9aa37ee89ab6d7d556b" + integrity sha512-BWJhvfnwbKTqEKS3mXWY8jfSI5zteSNEVfA06vA7+JPpiGrRWMIvolRiSQ4aeDAPXLoHqulzAm9PHmkSEnNsVw== "@box/types@^0.2.1": version "0.2.1" From fd5fb62fa47a4706061dcde5f152c0d20db6ab3b Mon Sep 17 00:00:00 2001 From: John Camden Date: Tue, 28 Oct 2025 16:11:14 -0400 Subject: [PATCH 2/7] fix(metadata): add await for loading indicator in taxonomy stories --- ...MetadataSidebarRedesign-visual.stories.tsx | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx index 4463026d30..c96f377a7a 100644 --- a/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx +++ b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx @@ -48,6 +48,19 @@ const mockLogger = { }, }; +// I'm not exactly sure why, but the metadata sidebar shows a loading indicator for several seconds with taxonomies +// This await is needed in order for taxonomy tests to pass. +const waitForLoadingToComplete = async (canvas: ReturnType) => { + const loadingIndicator = await canvas.findByRole('status', { name: 'Loading' }); + expect(loadingIndicator).toBeInTheDocument(); + await waitFor( + async () => { + expect(loadingIndicator).not.toBeInTheDocument(); + }, + { timeout: 10000 }, + ); +}; + export const AddTemplateDropdownMenuOn = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); @@ -76,6 +89,7 @@ export const AddTemplateDropdownMenuOnEmpty = { }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); + const addTemplateButton = await canvas.findByRole('button', { name: 'Add template' }, { timeout: 2000 }); expect(addTemplateButton).toBeInTheDocument(); @@ -616,6 +630,8 @@ export const ViewMultilevelTaxonomy: StoryObj = play: async ({ canvasElement }) => { const canvas = within(canvasElement); + await waitForLoadingToComplete(canvas); + await waitFor(async () => { const multilevelOptionButton = canvas.getByRole('button', { name: 'London' }); @@ -644,6 +660,8 @@ export const ViewSinglelevelTaxonomy: StoryObj = play: async ({ canvasElement }) => { const canvas = within(canvasElement); + await waitForLoadingToComplete(canvas); + await waitFor(async () => { const singlelevelOptionButton = canvas.getByRole('button', { name: 'Blue' }); @@ -663,6 +681,8 @@ export const EditMultilevelTaxonomy: StoryObj = play: async ({ canvasElement }) => { const canvas = within(canvasElement); + await waitForLoadingToComplete(canvas); + const editButton = await waitFor(() => canvas.getByRole('button', { name: 'Edit My Taxonomy' })); await userEvent.click(editButton); @@ -706,6 +726,8 @@ export const EditSinglelevelTaxonomy: StoryObj = play: async ({ canvasElement }) => { const canvas = within(canvasElement); + await waitForLoadingToComplete(canvas); + const editButton = await waitFor(() => canvas.getByRole('button', { name: 'Edit My Taxonomy' })); await userEvent.click(editButton); From eb83b5c61752e178680077168ac13d16f86ab2b9 Mon Sep 17 00:00:00 2001 From: John Camden Date: Thu, 30 Oct 2025 10:26:49 -0400 Subject: [PATCH 3/7] fix(metadata): Stabilize taxonomy stories: await loading, expand by label, scope queries --- ...MetadataSidebarRedesign-visual.stories.tsx | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx index c96f377a7a..8dc48f7f8e 100644 --- a/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx +++ b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx @@ -683,27 +683,32 @@ export const EditMultilevelTaxonomy: StoryObj = await waitForLoadingToComplete(canvas); - const editButton = await waitFor(() => canvas.getByRole('button', { name: 'Edit My Taxonomy' })); - + const editButton = await canvas.findByRole('button', { name: 'Edit My Taxonomy' }); await userEvent.click(editButton); - const multilevelInput = canvas.getByRole('combobox'); - const optionChip = canvas.getByRole('button', { name: 'London' }); - + const multilevelInput = await canvas.findByRole('combobox'); + const optionChip = await canvas.findByRole('button', { name: 'London' }); expect(multilevelInput).toBeInTheDocument(); expect(optionChip).toBeInTheDocument(); await userEvent.click(multilevelInput); - const listbox = await waitFor(() => canvas.getByRole('listbox')); - expect(listbox).toBeInTheDocument(); + // Scope subsequent queries to the listbox to avoid race conditions with other UI + const listbox = await canvas.findByRole('listbox'); + const listboxCanvas = within(listbox); const expandButtons = await waitFor(() => canvas.getAllByRole('button', { name: 'Expand branch' })); - await userEvent.click(expandButtons[1]); + await expandBranch('Japan', 'Hokkaido'); + await expandBranch('Hokkaido', 'Sapporo'); - const hokkaidoOption = await waitFor(() => canvas.getByText('Hokkaido')); - expect(hokkaidoOption).toBeInTheDocument(); + // Re-scope to the active listbox before selecting the leaf. + // Expanding branches can trigger a tree re-render that unmounts the previous + // popup content. Re-querying the listbox here ensures we don't act on a stale + // element reference and avoids intermittent "element not in the document" errors. + const activeListbox = await canvas.findByRole('listbox'); + const activeListboxCanvas = within(activeListbox); + await userEvent.click(await activeListboxCanvas.findByRole('treeitem', { name: 'Sapporo' })); const nestedExpandButtons = await waitFor(() => screen.getAllByRole('button', { name: 'Expand branch' })); From 9b27f2f65e526b489d97d23bbf0f47b4a329e465 Mon Sep 17 00:00:00 2001 From: John Camden Date: Mon, 3 Nov 2025 14:33:21 -0500 Subject: [PATCH 4/7] feat(metadata): Add missing API mocks to taxonomy tests to fix failures after package upgrades --- .../stories/__mocks__/TaxonomyMocks.ts | 9 ++++++- ...MetadataSidebarRedesign-visual.stories.tsx | 27 +++++++++---------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/elements/content-sidebar/stories/__mocks__/TaxonomyMocks.ts b/src/elements/content-sidebar/stories/__mocks__/TaxonomyMocks.ts index 899d5fd3ba..307b95dd77 100644 --- a/src/elements/content-sidebar/stories/__mocks__/TaxonomyMocks.ts +++ b/src/elements/content-sidebar/stories/__mocks__/TaxonomyMocks.ts @@ -2,7 +2,8 @@ import { http, HttpResponse } from 'msw'; import type { HttpHandler } from 'msw'; import { DEFAULT_HOSTNAME_API } from '../../../../constants'; -import { fileIdWithMetadata, mockFileRequest } from './MetadataSidebarRedesignedMocks'; +import { fileIdWithMetadata, mockFileRequest, mockGlobalMetadataTemplates } from './MetadataSidebarRedesignedMocks'; +import { mockUserRequest } from '../../../common/__mocks__/mockRequests'; const apiV2Path = `${DEFAULT_HOSTNAME_API}/2.0`; @@ -397,9 +398,15 @@ export const mockSinglelevelTaxonomyNodes = { }; export const taxonomyMockHandlers: HttpHandler[] = [ + http.get(mockUserRequest.url, () => { + return HttpResponse.json(mockUserRequest.response); + }), http.get(mockFileRequest.url, () => { return HttpResponse.json(mockFileRequest.response); }), + http.get(mockGlobalMetadataTemplates.url, () => { + return HttpResponse.json(mockGlobalMetadataTemplates.response); + }), http.get(mockMetadataInstancesWithTaxonomy.url, () => { return HttpResponse.json(mockMetadataInstancesWithTaxonomy.response); }), diff --git a/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx index 8dc48f7f8e..11850c813d 100644 --- a/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx +++ b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx @@ -687,28 +687,25 @@ export const EditMultilevelTaxonomy: StoryObj = await userEvent.click(editButton); const multilevelInput = await canvas.findByRole('combobox'); - const optionChip = await canvas.findByRole('button', { name: 'London' }); - expect(multilevelInput).toBeInTheDocument(); - expect(optionChip).toBeInTheDocument(); - await userEvent.click(multilevelInput); - // Scope subsequent queries to the listbox to avoid race conditions with other UI const listbox = await canvas.findByRole('listbox'); const listboxCanvas = within(listbox); - const expandButtons = await waitFor(() => canvas.getAllByRole('button', { name: 'Expand branch' })); + const japanLabel = await listboxCanvas.findByText('Japan'); + const japanTreeitem = japanLabel.closest('[role="treeitem"]') as HTMLElement | null; + expect(japanTreeitem).not.toBeNull(); + const expander = within(japanTreeitem as HTMLElement).getByRole('button', { name: 'Expand branch' }); + await userEvent.click(expander); - await expandBranch('Japan', 'Hokkaido'); - await expandBranch('Hokkaido', 'Sapporo'); + const hokkaidoLabel = await listboxCanvas.findByText('Hokkaido'); + const hokkaidoTreeitem = hokkaidoLabel.closest('[role="treeitem"]') as HTMLElement | null; + expect(hokkaidoTreeitem).not.toBeNull(); + const hokkaidoExpander = within(hokkaidoTreeitem as HTMLElement).getByRole('button', { name: 'Expand branch' }); + await userEvent.click(hokkaidoExpander); - // Re-scope to the active listbox before selecting the leaf. - // Expanding branches can trigger a tree re-render that unmounts the previous - // popup content. Re-querying the listbox here ensures we don't act on a stale - // element reference and avoids intermittent "element not in the document" errors. - const activeListbox = await canvas.findByRole('listbox'); - const activeListboxCanvas = within(activeListbox); - await userEvent.click(await activeListboxCanvas.findByRole('treeitem', { name: 'Sapporo' })); + const sapporoTreeitem = await listboxCanvas.findByRole('treeitem', { name: 'Sapporo' }); + await userEvent.click(sapporoTreeitem); const nestedExpandButtons = await waitFor(() => screen.getAllByRole('button', { name: 'Expand branch' })); From 277379d60c5e3e1655709dd60bc43e626233c9d2 Mon Sep 17 00:00:00 2001 From: John Camden Date: Mon, 3 Nov 2025 14:50:16 -0500 Subject: [PATCH 5/7] feat(metadata): Update multilevel taxonomy test after rebase --- ...MetadataSidebarRedesign-visual.stories.tsx | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx index 11850c813d..3344b756f4 100644 --- a/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx +++ b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx @@ -686,40 +686,40 @@ export const EditMultilevelTaxonomy: StoryObj = const editButton = await canvas.findByRole('button', { name: 'Edit My Taxonomy' }); await userEvent.click(editButton); - const multilevelInput = await canvas.findByRole('combobox'); - await userEvent.click(multilevelInput); - - const listbox = await canvas.findByRole('listbox'); - const listboxCanvas = within(listbox); - - const japanLabel = await listboxCanvas.findByText('Japan'); - const japanTreeitem = japanLabel.closest('[role="treeitem"]') as HTMLElement | null; - expect(japanTreeitem).not.toBeNull(); - const expander = within(japanTreeitem as HTMLElement).getByRole('button', { name: 'Expand branch' }); - await userEvent.click(expander); - - const hokkaidoLabel = await listboxCanvas.findByText('Hokkaido'); - const hokkaidoTreeitem = hokkaidoLabel.closest('[role="treeitem"]') as HTMLElement | null; - expect(hokkaidoTreeitem).not.toBeNull(); - const hokkaidoExpander = within(hokkaidoTreeitem as HTMLElement).getByRole('button', { name: 'Expand branch' }); - await userEvent.click(hokkaidoExpander); - - const sapporoTreeitem = await listboxCanvas.findByRole('treeitem', { name: 'Sapporo' }); - await userEvent.click(sapporoTreeitem); - - const nestedExpandButtons = await waitFor(() => screen.getAllByRole('button', { name: 'Expand branch' })); - - await userEvent.click(nestedExpandButtons[2]); - - const sapporoOption = await waitFor(() => canvas.getByRole('treeitem', { name: 'Sapporo' })); - - expect(sapporoOption).toBeInTheDocument(); - expect(sapporoOption).toHaveAttribute('aria-selected', 'false'); - - await userEvent.click(sapporoOption); + const navigateToSapporoTreeItem = async () => { + const multilevelInput = await canvas.findByRole('combobox'); + await userEvent.click(multilevelInput); + + const listbox = await canvas.findByRole('listbox'); + const listboxCanvas = within(listbox); + + const japanLabel = await listboxCanvas.findByText('Japan'); + const japanTreeitem = japanLabel.closest('[role="treeitem"]') as HTMLElement | null; + expect(japanTreeitem).not.toBeNull(); + const expander = within(japanTreeitem as HTMLElement).getByRole('button', { name: 'Expand branch' }); + await userEvent.click(expander); + + const hokkaidoLabel = await listboxCanvas.findByText('Hokkaido'); + const hokkaidoTreeitem = hokkaidoLabel.closest('[role="treeitem"]') as HTMLElement | null; + expect(hokkaidoTreeitem).not.toBeNull(); + const hokkaidoExpander = within(hokkaidoTreeitem as HTMLElement).getByRole('button', { + name: 'Expand branch', + }); + await userEvent.click(hokkaidoExpander); + + const sapporoTreeitem = await listboxCanvas.findByRole('treeitem', { name: 'Sapporo' }); + return sapporoTreeitem; + }; + + const unselectedSapporoTreeitem = await navigateToSapporoTreeItem(); + expect(unselectedSapporoTreeitem).toHaveAttribute('aria-selected', 'false'); + await userEvent.click(unselectedSapporoTreeitem); const sapporoSelection = await waitFor(() => canvas.getByRole('gridcell', { name: 'Sapporo' })); expect(sapporoSelection).toBeInTheDocument(); + + const selectedSapporoTreeitem = await navigateToSapporoTreeItem(); + expect(selectedSapporoTreeitem).toHaveAttribute('aria-selected', 'true'); }, }; From 2b89a946698ebfe4d5d8e3ca8e3a18a251af813c Mon Sep 17 00:00:00 2001 From: John Camden Date: Tue, 4 Nov 2025 11:18:34 -0500 Subject: [PATCH 6/7] feat(metadata): Remove obsolete comment --- .../stories/tests/MetadataSidebarRedesign-visual.stories.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx index 3344b756f4..8dbc9d3c50 100644 --- a/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx +++ b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx @@ -48,8 +48,6 @@ const mockLogger = { }, }; -// I'm not exactly sure why, but the metadata sidebar shows a loading indicator for several seconds with taxonomies -// This await is needed in order for taxonomy tests to pass. const waitForLoadingToComplete = async (canvas: ReturnType) => { const loadingIndicator = await canvas.findByRole('status', { name: 'Loading' }); expect(loadingIndicator).toBeInTheDocument(); From e1b5e1f6daca16139a8d2ffb01c357e20fa09824 Mon Sep 17 00:00:00 2001 From: John Camden Date: Tue, 4 Nov 2025 12:30:51 -0500 Subject: [PATCH 7/7] feat(metadata): Bump peer dep version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e26ff0f53b..870ed2841c 100644 --- a/package.json +++ b/package.json @@ -302,7 +302,7 @@ "@box/box-ai-content-answers": "^0.139.0", "@box/box-item-type-selector": "^0.73.1", "@box/cldr-data": ">=34.2.0", - "@box/combobox-with-api": "^0.34.9", + "@box/combobox-with-api": "^1.3.11", "@box/copy-input": "^1.5.3", "@box/item-icon": "^0.27.1", "@box/metadata-editor": "^1.5.3",