diff --git a/changelog/entries/unreleased/bug/fixes_the_link_to_table_field_not_showing_the_view_it_is_lim.json b/changelog/entries/unreleased/bug/fixes_the_link_to_table_field_not_showing_the_view_it_is_lim.json new file mode 100644 index 0000000000..359121e9ba --- /dev/null +++ b/changelog/entries/unreleased/bug/fixes_the_link_to_table_field_not_showing_the_view_it_is_lim.json @@ -0,0 +1,9 @@ +{ + "type": "bug", + "message": "Fixes the link to table field not showing the view it is limited to when editing", + "issue_origin": "github", + "issue_number": null, + "domain": "database", + "bullet_points": [], + "created_at": "2026-06-25" +} diff --git a/e2e-tests/tests/database/link_row_limit_selection_view.spec.ts b/e2e-tests/tests/database/link_row_limit_selection_view.spec.ts new file mode 100644 index 0000000000..6688794870 --- /dev/null +++ b/e2e-tests/tests/database/link_row_limit_selection_view.spec.ts @@ -0,0 +1,72 @@ +import { expect, test } from "../baserowTest"; +import { createDatabase } from "../../fixtures/database/database"; +import { createTable } from "../../fixtures/database/table"; +import { createField } from "../../fixtures/database/field"; +import { getDefaultGridView, patchView } from "../../fixtures/database/view"; +import { TablePage } from "../../pages/database/tablePage"; + +// Regression: opening a link-row field that is already limited to a view used +// to show an empty "No items available" dropdown because the linked table's +// views were never fetched on mount, so the saved selection looked gone. +test.describe("Link-row 'Limit selection to view'", () => { + test.describe.configure({ timeout: 60_000 }); + + test("editing a field limited to a view shows the saved view", async ({ + page, + goto, + workspacePage, + }) => { + const user = workspacePage.user; + const workspace = workspacePage.workspace; + + const database = await createDatabase(user, "Limit View DB", workspace); + + // Linked table whose grid view limits the selection. The view gets a + // distinctive name so it cannot be confused with the editing table's own + // default "Grid" view. + const projects = await createTable(user, "Projects", database, [ + ["Name"], + ["Apollo"], + ]); + const limitView = await getDefaultGridView(user, projects); + await patchView(user, limitView, { name: "Active Projects" }); + + // Table holding the link-row field, already limited to the view above. + const customers = await createTable(user, "Customers", database, [ + ["Name"], + ["Ada"], + ]); + await createField( + user, + "Linked projects", + "link_row", + { + link_row_table_id: projects.id, + link_row_limit_selection_view_id: limitView.id, + }, + customers, + ); + + const tablePage = new TablePage({ page, goto }); + await tablePage.goToTable(customers); + + // Open the link-row field's context menu, then "Edit field". + const fieldHeader = page + .locator(".grid-view__description", { hasText: "Linked projects" }) + .first(); + await fieldHeader.hover(); + await fieldHeader.locator(".grid-view__description-icon-trigger").click(); + await page + .locator(".context__menu-item-link") + .getByText("Edit field", { exact: true }) + .click(); + + // The view the selection is limited to must be shown as the dropdown's + // selected value, instead of falling back to the "Make a choice" + // placeholder (the "my view selection is gone" bug). + await expect( + page.locator(".dropdown__selected-text", { hasText: "Active Projects" }) + ).toBeVisible(); + await expect(page.getByText("Make a choice")).toBeHidden(); + }); +}); diff --git a/web-frontend/modules/database/components/field/FieldLinkRowSubForm.vue b/web-frontend/modules/database/components/field/FieldLinkRowSubForm.vue index c2d0bdf862..b9861abe1a 100644 --- a/web-frontend/modules/database/components/field/FieldLinkRowSubForm.vue +++ b/web-frontend/modules/database/components/field/FieldLinkRowSubForm.vue @@ -176,6 +176,10 @@ export default { this.initialLinkRowTable == null || this.defaultValues.link_row_related_field != null this.limitToViewToggle = !!this.values.link_row_limit_selection_view_id + // For an existing field the table id is already set before mount, so the + // change-watcher that loads related views runs while limitToViewToggle is + // still false and bails. Load them here so the saved selection is shown. + this.loadViewsIfNeeded() }, methods: { diff --git a/web-frontend/test/unit/database/components/field/fieldLinkRowSubForm.spec.js b/web-frontend/test/unit/database/components/field/fieldLinkRowSubForm.spec.js new file mode 100644 index 0000000000..e4dbfc77ba --- /dev/null +++ b/web-frontend/test/unit/database/components/field/fieldLinkRowSubForm.spec.js @@ -0,0 +1,109 @@ +import { TestApp } from '@baserow/test/helpers/testApp' +import flushPromises from 'flush-promises' + +import FieldLinkRowSubForm from '@baserow/modules/database/components/field/FieldLinkRowSubForm' +import DropdownItem from '@baserow/modules/core/components/DropdownItem' +import ViewService from '@baserow/modules/database/services/view' + +vi.mock('@baserow/modules/database/services/view', () => ({ + default: vi.fn(), +})) + +const DATABASE_ID = 100 +const LINKED_TABLE_ID = 2 +const LIMIT_VIEW_ID = 10 + +const collaborativeGridView = { + id: LIMIT_VIEW_ID, + name: 'Active Projects', + type: 'grid', + order: 1, + ownership_type: 'collaborative', +} + +const mockFetchAll = (views) => { + const fetchAll = vi.fn().mockResolvedValue({ data: views }) + ViewService.mockReturnValue({ fetchAll }) + return fetchAll +} + +describe('FieldLinkRowSubForm', () => { + let testApp = null + + beforeEach(() => { + testApp = new TestApp() + }) + + afterEach(() => { + testApp.afterEach() + vi.restoreAllMocks() + }) + + const database = { id: DATABASE_ID, workspace: { id: 1 } } + const table = { id: 1, name: 'Customers', database_id: DATABASE_ID } + + const baseProps = { + table, + database, + view: {}, + allFieldsInTable: [], + fieldType: 'link_row', + } + + const mountComponent = async (defaultValues) => { + // The component reads the linkable tables from the application store. + await testApp.store.dispatch('application/forceCreate', { + id: DATABASE_ID, + type: 'database', + workspace: { id: 1 }, + tables: [ + { id: LINKED_TABLE_ID, name: 'Projects', database_id: DATABASE_ID }, + ], + }) + const wrapper = await testApp.mount(FieldLinkRowSubForm, { + propsData: { ...baseProps, defaultValues }, + }) + await flushPromises() + return wrapper + } + + const renderedItemValues = (wrapper) => + wrapper.findAllComponents(DropdownItem).map((item) => item.props('value')) + + const selectedDropdownTexts = (wrapper) => + wrapper.findAll('.dropdown__selected-text').map((el) => el.text()) + + test('opening a field already limited to a view loads and shows that view', async () => { + const fetchAll = mockFetchAll([collaborativeGridView]) + const wrapper = await mountComponent({ + link_row_table_id: LINKED_TABLE_ID, + link_row_limit_selection_view_id: LIMIT_VIEW_ID, + link_row_multiple_relationships: true, + }) + + // The user-visible symptom: the dropdown shows the saved view's name as + // its selected value, instead of falling back to the "Make a choice" + // placeholder (the "my view selection is gone" bug). + expect(selectedDropdownTexts(wrapper)).toContain('Active Projects') + expect(wrapper.text()).not.toContain('Make a choice') + // The previously selected view is listed as an option, so the dropdown + // is not the empty "No items available" list. + expect(renderedItemValues(wrapper)).toContain(LIMIT_VIEW_ID) + // ...because the linked table's views are fetched on mount. + expect(fetchAll).toHaveBeenCalled() + expect(fetchAll.mock.calls[0][0]).toBe(LINKED_TABLE_ID) + }) + + test('opening a field without a limit view does not fetch views on mount', async () => { + const fetchAll = mockFetchAll([collaborativeGridView]) + const wrapper = await mountComponent({ + link_row_table_id: LINKED_TABLE_ID, + link_row_limit_selection_view_id: null, + link_row_multiple_relationships: true, + }) + + // Nothing to restore, so the views dropdown stays hidden and unfetched. + expect(fetchAll).not.toHaveBeenCalled() + expect(renderedItemValues(wrapper)).not.toContain(LIMIT_VIEW_ID) + }) +})