Skip to content
Open
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
6 changes: 4 additions & 2 deletions packages/bindx-ui/src/datagrid/columns/has-one-column.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { type ReactElement, type ReactNode, useCallback } from 'react'
import type { EntityAccessor, EntityDef, FieldRef } from '@contember/bindx'
import { isPersistedId } from '@contember/bindx'
import {
createRelationColumn,
hasOneColumnDef,
Expand Down Expand Up @@ -106,8 +107,9 @@ const relationUI = {
<RelationFilterUI {...ctx} />
),
renderCellWrapper: ({ content, item, fieldName, filterName, fieldRef }: RelationCellWrapperContext) => {
const id = getRelatedAccessor(item, fieldName)?.id ?? null
if (!id) return content
const id = getRelatedAccessor(item, fieldName)?.id
// An empty relation still has a placeholder id; filtering on it is rejected by the server.
if (!id || !isPersistedId(id)) return content
return (
<DataGridHasOneTooltip field={fieldRef} name={filterName} id={id}>
<DataGridTooltipLabel>{content}</DataGridTooltipLabel>
Expand Down
85 changes: 85 additions & 0 deletions tests/react/dataview/hasOneColumnEmptyCellFilter.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Regression test for https://github.com/contember/bindx/issues/136
//
// A nullable has-one with no related entity still yields a handle whose `id`
// is a disconnected placeholder id. The cell must not offer include/exclude
// actions on it: a filter on that id cannot match anything, and a server
// rejects it as an invalid id.
import '../../setup'
import { afterEach, describe, expect, test } from 'bun:test'
import { act, cleanup, render, waitFor } from '@testing-library/react'
import React from 'react'
import { BindxProvider, MockAdapter } from '@contember/bindx-react'
import { DataGrid } from '@contember/bindx-dataview'
import { DataGridAutoTable, DataGridHasOneColumn } from '@contember/bindx-ui'
import { schema, testSchema } from '../../shared/index.js'

afterEach(async () => {
await act(async () => {
cleanup()
await new Promise(resolve => setTimeout(resolve, 0))
})
})

function renderGrid(): HTMLElement {
const adapter = new MockAdapter({
Article: {
'article-1': {
id: 'article-1',
title: 'With author',
content: '',
author: { id: 'author-1', name: 'Alice', email: 'alice@example.com' },
tags: [],
},
'article-2': {
id: 'article-2',
title: 'Without author',
content: '',
author: null,
tags: [],
},
},
Author: {
'author-1': { id: 'author-1', name: 'Alice', email: 'alice@example.com' },
},
Tag: {},
Location: {},
}, { delay: 0 })

const { container } = render(
<BindxProvider adapter={adapter} schema={testSchema}>
<DataGrid entity={schema.Article}>
{it => (
<>
<DataGridHasOneColumn field={it.author} header="Author">
{author => author.name.value ?? '—'}
</DataGridHasOneColumn>
<DataGridAutoTable />
</>
)}
</DataGrid>
</BindxProvider>,
)
return container
}

function authorCells(container: HTMLElement): HTMLElement[] {
return Array.from(container.querySelectorAll<HTMLElement>('[data-testid="datagrid-cell-author"]'))
}

describe('has-one column cell filter affordance', () => {
test('should not offer include/exclude on a cell whose relation is empty', async () => {
const container = renderGrid()

await waitFor(() => {
expect(authorCells(container)).toHaveLength(2)
})

const connectedCell = authorCells(container).find(cell => cell.textContent?.includes('Alice'))
const emptyCell = authorCells(container).find(cell => cell.textContent?.includes('—'))
expect(connectedCell).toBeDefined()
expect(emptyCell).toBeDefined()

expect(connectedCell!.querySelector('[data-bindx-tooltip]')).not.toBeNull()
expect(emptyCell!.querySelector('[data-bindx-tooltip]')).toBeNull()
})
})
Loading