Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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"
}
72 changes: 72 additions & 0 deletions e2e-tests/tests/database/link_row_limit_selection_view.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
})
})
Loading