chore: added e2e tests for filter bar (#42709)

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Chore: E2E tests to cover the full keyboard and mouse flow for the AI
Filters

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Tests**
* Added a comprehensive end-to-end test suite for the Filter Bar:
creation/removal of filters, keyboard and dropdown navigation,
operator/value flows, boolean/date/timestamp presets, multiple
simultaneous filters, and grid result validation.
* **Tests / Utilities**
* Added reusable test helpers to set up tables, interact with the filter
bar, and wait for data updates.
* **Style / Test hooks**
* Added test IDs and highlight attributes to filter UI elements to
improve test reliability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Ali Waseem authored and GitHub committed 2026-02-13 08:56:59 -07:00
1 parent 7fba97517c
commit 1b319f8856
5 files changed
+904

No files matched your search

+813
View File
@@ -0,0 +1,813 @@
import { expect } from '@playwright/test'
import { createTable, dropTable, query } from '../utils/db/index.js'
import {
addFilter,
addFilterWithDropdownValue,
getFilterBarInput,
navigateToTable,
selectColumnFilter,
selectOperator,
selectOperatorByClick,
setupFilterBarPage,
} from '../utils/filter-bar-helpers.js'
import { test } from '../utils/test.js'
import { toUrl } from '../utils/to-url.js'
const tableNamePrefix = 'pw_filter_bar'
function getDateValue(daysAgo: number): string {
const date = new Date(Date.now() - daysAgo * 86400000)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
test.describe('Filter Bar', () => {
test.beforeEach(async ({ page, ref }) => {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
})
test.describe('Basic Filter Operations', () => {
test('selecting a column creates a filter condition', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_col_sel`
const columnName = 'name'
await createTable(tableName, columnName, [{ name: 'Alice' }])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await selectColumnFilter(page, columnName)
await expect(page.getByTestId(`filter-condition-${columnName}`)).toBeVisible()
await expect(page.getByTestId(`filter-operator-${columnName}`)).toBeFocused()
} finally {
await dropTable(tableName)
}
})
test('entering value filters the grid', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_val_filt`
const columnName = 'name'
await createTable(tableName, columnName, [
{ name: 'Alice' },
{ name: 'Bob' },
{ name: 'Charlie' },
])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilter(page, ref, columnName, '=', 'Alice')
await expect(page.getByRole('gridcell', { name: 'Alice' })).toBeVisible()
await expect(page.getByRole('gridcell', { name: 'Bob' })).not.toBeVisible()
await expect(page.getByRole('gridcell', { name: 'Charlie' })).not.toBeVisible()
} finally {
await dropTable(tableName)
}
})
test('removing filter via X button restores all data', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_remove_x`
const columnName = 'name'
await createTable(tableName, columnName, [{ name: 'Alice' }, { name: 'Bob' }])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilter(page, ref, columnName, '=', 'Alice')
await expect(page.getByRole('gridcell', { name: 'Bob' })).not.toBeVisible()
await page.getByTestId(`filter-remove-${columnName}`).click()
await expect(page.getByTestId(`filter-condition-${columnName}`)).not.toBeVisible()
await expect(page.getByRole('gridcell', { name: 'Alice' })).toBeVisible()
await expect(page.getByRole('gridcell', { name: 'Bob' })).toBeVisible()
} finally {
await dropTable(tableName)
}
})
test('multiple filters can be added', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_multi`
await query(
`CREATE TABLE IF NOT EXISTS ${tableName} (
id bigint generated by default as identity primary key,
first_name text,
last_name text
)`
)
await query(
`INSERT INTO ${tableName} (first_name, last_name) VALUES ('Alice', 'Smith'), ('Bob', 'Smith'), ('Alice', 'Jones')`
)
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilter(page, ref, 'first_name', '=', 'Alice')
await addFilter(page, ref, 'last_name', '=', 'Smith')
await expect(page.getByTestId('filter-condition-first_name')).toBeVisible()
await expect(page.getByTestId('filter-condition-last_name')).toBeVisible()
} finally {
await dropTable(tableName)
}
})
})
test.describe('Keyboard Navigation - Freeform Input', () => {
test('Enter selects column from dropdown', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_enter_col`
const columnName = 'name'
await createTable(tableName, columnName, [{ name: 'Alice' }])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
const freeformInput = getFilterBarInput(page)
await freeformInput.click()
await freeformInput.fill(columnName)
await expect(page.getByTestId(`filter-menu-item-${columnName}`)).toBeVisible()
await page.keyboard.press('Enter')
await expect(page.getByTestId(`filter-condition-${columnName}`)).toBeVisible()
} finally {
await dropTable(tableName)
}
})
test('Tab selects column from dropdown', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_tab_col`
const columnName = 'name'
await createTable(tableName, columnName, [{ name: 'Alice' }])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
const freeformInput = getFilterBarInput(page)
await freeformInput.click()
await freeformInput.fill(columnName)
await expect(page.getByTestId(`filter-menu-item-${columnName}`)).toBeVisible()
await page.keyboard.press('Tab')
await expect(page.getByTestId(`filter-condition-${columnName}`)).toBeVisible()
} finally {
await dropTable(tableName)
}
})
test('ArrowDown/ArrowUp navigates dropdown items', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_arrow_nav`
await query(
`CREATE TABLE IF NOT EXISTS ${tableName} (
id bigint generated by default as identity primary key,
alpha text,
beta text,
gamma text
)`
)
await query(`INSERT INTO ${tableName} (alpha, beta, gamma) VALUES ('a', 'b', 'g')`)
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
const freeformInput = getFilterBarInput(page)
await freeformInput.click()
// Items: id (0), alpha (1), beta (2), gamma (3)
await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown')
await page.keyboard.press('Enter')
await expect(page.getByTestId('filter-condition-beta')).toBeVisible()
} finally {
await dropTable(tableName)
}
})
test('Backspace on empty input highlights last filter, second Backspace deletes it', async ({
page,
ref,
}) => {
const tableName = `${tableNamePrefix}_bksp_hl_del`
const columnName = 'name'
await createTable(tableName, columnName, [{ name: 'Alice' }])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilter(page, ref, columnName, '=', 'Alice')
const freeformInput = getFilterBarInput(page)
await freeformInput.click()
await page.keyboard.press('Backspace')
await expect(page.getByTestId(`filter-condition-${columnName}`)).toHaveAttribute(
'data-highlighted',
'true'
)
await page.keyboard.press('Backspace')
await expect(page.getByTestId(`filter-condition-${columnName}`)).not.toBeVisible()
} finally {
await dropTable(tableName)
}
})
test('ArrowLeft highlights previous condition', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_arrowl_hl`
const columnName = 'name'
await createTable(tableName, columnName, [{ name: 'Alice' }])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilter(page, ref, columnName, '=', 'Alice')
const freeformInput = getFilterBarInput(page)
await freeformInput.click()
await page.keyboard.press('ArrowLeft')
await expect(page.getByTestId(`filter-condition-${columnName}`)).toHaveAttribute(
'data-highlighted',
'true'
)
} finally {
await dropTable(tableName)
}
})
test('ArrowRight clears highlight at end', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_arrowr_cl`
const columnName = 'name'
await createTable(tableName, columnName, [{ name: 'Alice' }])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilter(page, ref, columnName, '=', 'Alice')
const freeformInput = getFilterBarInput(page)
await freeformInput.click()
await page.keyboard.press('ArrowLeft')
await expect(page.getByTestId(`filter-condition-${columnName}`)).toHaveAttribute(
'data-highlighted',
'true'
)
await page.keyboard.press('ArrowRight')
await expect(page.getByTestId(`filter-condition-${columnName}`)).not.toHaveAttribute(
'data-highlighted',
'true'
)
} finally {
await dropTable(tableName)
}
})
test('Escape clears highlight', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_esc_cl`
const columnName = 'name'
await createTable(tableName, columnName, [{ name: 'Alice' }])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilter(page, ref, columnName, '=', 'Alice')
const freeformInput = getFilterBarInput(page)
await freeformInput.click()
await page.keyboard.press('ArrowLeft')
await expect(page.getByTestId(`filter-condition-${columnName}`)).toHaveAttribute(
'data-highlighted',
'true'
)
await page.keyboard.press('Escape')
await expect(page.getByTestId(`filter-condition-${columnName}`)).not.toHaveAttribute(
'data-highlighted',
'true'
)
} finally {
await dropTable(tableName)
}
})
test('Enter on highlighted condition focuses value input', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_enter_hl`
const columnName = 'name'
await createTable(tableName, columnName, [{ name: 'Alice' }])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilter(page, ref, columnName, '=', 'Alice')
const freeformInput = getFilterBarInput(page)
await freeformInput.click()
await page.keyboard.press('Backspace')
await expect(page.getByTestId(`filter-condition-${columnName}`)).toHaveAttribute(
'data-highlighted',
'true'
)
await page.keyboard.press('Enter')
await expect(page.getByTestId(`filter-value-${columnName}`)).toBeFocused()
} finally {
await dropTable(tableName)
}
})
})
test.describe('Keyboard Navigation - Operator Input', () => {
test('Enter selects operator and focuses value', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_op_enter`
const columnName = 'name'
await createTable(tableName, columnName, [{ name: 'Alice' }])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await selectColumnFilter(page, columnName)
await page.keyboard.press('Enter')
await expect(page.getByTestId(`filter-value-${columnName}`)).toBeFocused()
} finally {
await dropTable(tableName)
}
})
test('Backspace on empty operator removes condition', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_op_bksp`
const columnName = 'name'
await createTable(tableName, columnName, [{ name: 'Alice' }])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await selectColumnFilter(page, columnName)
const operatorInput = page.getByTestId(`filter-operator-${columnName}`)
await operatorInput.fill('')
await page.keyboard.press('Backspace')
await expect(page.getByTestId(`filter-condition-${columnName}`)).not.toBeVisible()
} finally {
await dropTable(tableName)
}
})
})
test.describe('Keyboard Navigation - Value Input', () => {
test('Enter on value moves focus to group freeform', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_val_enter`
const columnName = 'name'
await createTable(tableName, columnName, [{ name: 'Alice' }])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilter(page, ref, columnName, '=', 'Alice')
await expect(getFilterBarInput(page)).toBeFocused()
} finally {
await dropTable(tableName)
}
})
test('Backspace on empty value moves to operator', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_val_bksp`
const columnName = 'name'
await createTable(tableName, columnName, [{ name: 'Alice' }])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await selectColumnFilter(page, columnName)
await selectOperator(page, columnName, '=')
await page.keyboard.press('Backspace')
await expect(page.getByTestId(`filter-operator-${columnName}`)).toBeFocused()
} finally {
await dropTable(tableName)
}
})
test('ArrowLeft at position 0 moves to previous condition value', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_val_arrowl`
await query(
`CREATE TABLE IF NOT EXISTS ${tableName} (
id bigint generated by default as identity primary key,
first_name text,
last_name text
)`
)
await query(
`INSERT INTO ${tableName} (first_name, last_name) VALUES ('Alice', 'Smith'), ('Bob', 'Jones')`
)
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilter(page, ref, 'first_name', '=', 'Alice')
await addFilter(page, ref, 'last_name', '=', 'Smith')
const lastNameValue = page.getByTestId('filter-value-last_name')
await lastNameValue.click()
await page.keyboard.press('Home')
await page.keyboard.press('ArrowLeft')
await expect(page.getByTestId('filter-value-first_name')).toBeFocused()
} finally {
await dropTable(tableName)
}
})
test('ArrowRight at end moves to next condition or freeform', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_val_arrowr`
const columnName = 'name'
await createTable(tableName, columnName, [{ name: 'Alice' }])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilter(page, ref, columnName, '=', 'Alice')
// Use keyboard to focus value: highlight condition then Enter to avoid re-opening dropdown
const freeformInput = getFilterBarInput(page)
await freeformInput.click()
await page.keyboard.press('Backspace')
await page.keyboard.press('Enter')
await expect(page.getByTestId(`filter-value-${columnName}`)).toBeFocused()
await page.keyboard.press('End')
await page.keyboard.press('ArrowRight')
await expect(freeformInput).toBeFocused()
} finally {
await dropTable(tableName)
}
})
test('editing value in the middle preserves cursor position', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_cursor_pos`
const columnName = 'name'
await createTable(tableName, columnName, [{ name: 'test' }])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await selectColumnFilter(page, columnName)
await selectOperator(page, columnName, '=')
const valueInput = page.getByTestId(`filter-value-${columnName}`)
await valueInput.fill('HelloWorld')
// Move cursor to middle (after "Hello") and insert text
await page.keyboard.press('Home')
for (let i = 0; i < 5; i++) {
await page.keyboard.press('ArrowRight')
}
await page.keyboard.type('_Middle_')
// Verify the text was inserted in the middle, not appended at the end
await expect(valueInput).toHaveValue('Hello_Middle_World')
} finally {
await dropTable(tableName)
}
})
})
test.describe('Boolean Column Filters', () => {
test('filtering by true shows only true rows', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_bool_true`
await query(
`CREATE TABLE IF NOT EXISTS ${tableName} (
id bigint generated by default as identity primary key,
is_active boolean
)`
)
await query(`INSERT INTO ${tableName} (is_active) VALUES (true), (false), (true)`)
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilterWithDropdownValue(page, ref, 'is_active', '=', 'true')
const rows = page.locator('[role="row"]')
// Header row + 2 data rows with true
await expect(rows).toHaveCount(3)
} finally {
await dropTable(tableName)
}
})
test('filtering by false shows only false rows', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_bool_false`
await query(
`CREATE TABLE IF NOT EXISTS ${tableName} (
id bigint generated by default as identity primary key,
is_active boolean
)`
)
await query(`INSERT INTO ${tableName} (is_active) VALUES (true), (false), (true)`)
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilterWithDropdownValue(page, ref, 'is_active', '=', 'false')
const rows = page.locator('[role="row"]')
// Header row + 1 data row with false
await expect(rows).toHaveCount(2)
} finally {
await dropTable(tableName)
}
})
test('not equal operator with boolean filters correctly', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_bool_neq`
await query(
`CREATE TABLE IF NOT EXISTS ${tableName} (
id bigint generated by default as identity primary key,
is_active boolean
)`
)
await query(`INSERT INTO ${tableName} (is_active) VALUES (true), (false), (true)`)
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilterWithDropdownValue(page, ref, 'is_active', '<>', 'true')
const rows = page.locator('[role="row"]')
// Header row + 1 data row with false (not equal to true)
await expect(rows).toHaveCount(2)
} finally {
await dropTable(tableName)
}
})
})
test.describe('Date Column Filters', () => {
test('filtering by Today shows only today rows', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_date_today`
const todayValue = getDateValue(0)
await query(
`CREATE TABLE IF NOT EXISTS ${tableName} (
id bigint generated by default as identity primary key,
created_at date
)`
)
await query(
`INSERT INTO ${tableName} (created_at) VALUES
(CURRENT_DATE),
(CURRENT_DATE - INTERVAL '1 day'),
(CURRENT_DATE - INTERVAL '7 days')`
)
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilterWithDropdownValue(page, ref, 'created_at', '=', todayValue)
const rows = page.locator('[role="row"]')
await expect(rows).toHaveCount(2)
} finally {
await dropTable(tableName)
}
})
test('filtering by Yesterday shows only yesterday rows', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_date_yest`
const yesterdayValue = getDateValue(1)
await query(
`CREATE TABLE IF NOT EXISTS ${tableName} (
id bigint generated by default as identity primary key,
created_at date
)`
)
await query(
`INSERT INTO ${tableName} (created_at) VALUES
(CURRENT_DATE),
(CURRENT_DATE - INTERVAL '1 day'),
(CURRENT_DATE - INTERVAL '7 days')`
)
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilterWithDropdownValue(page, ref, 'created_at', '=', yesterdayValue)
const rows = page.locator('[role="row"]')
await expect(rows).toHaveCount(2)
} finally {
await dropTable(tableName)
}
})
test('filtering by Last 7 days with greater or equal operator', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_date_7d`
const last7DaysValue = getDateValue(7)
await query(
`CREATE TABLE IF NOT EXISTS ${tableName} (
id bigint generated by default as identity primary key,
created_at date
)`
)
await query(
`INSERT INTO ${tableName} (created_at) VALUES
(CURRENT_DATE),
(CURRENT_DATE - INTERVAL '3 days'),
(CURRENT_DATE - INTERVAL '7 days'),
(CURRENT_DATE - INTERVAL '30 days')`
)
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilterWithDropdownValue(page, ref, 'created_at', '>=', last7DaysValue)
const rows = page.locator('[role="row"]')
await expect(rows).toHaveCount(4)
} finally {
await dropTable(tableName)
}
})
test('date less than operator filters correctly', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_date_lt`
const todayValue = getDateValue(0)
await query(
`CREATE TABLE IF NOT EXISTS ${tableName} (
id bigint generated by default as identity primary key,
created_at date
)`
)
await query(
`INSERT INTO ${tableName} (created_at) VALUES
(CURRENT_DATE),
(CURRENT_DATE - INTERVAL '1 day'),
(CURRENT_DATE - INTERVAL '7 days')`
)
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilterWithDropdownValue(page, ref, 'created_at', '<', todayValue)
const rows = page.locator('[role="row"]')
await expect(rows).toHaveCount(3)
} finally {
await dropTable(tableName)
}
})
})
test.describe('Timestamp Column Filters', () => {
test('timestamp column shows date preset dropdown options', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_ts_today`
const todayValue = getDateValue(0)
await query(
`CREATE TABLE IF NOT EXISTS ${tableName} (
id bigint generated by default as identity primary key,
created_at timestamp
)`
)
await query(
`INSERT INTO ${tableName} (created_at) VALUES
(NOW()),
(NOW() - INTERVAL '1 day'),
(NOW() - INTERVAL '7 days')`
)
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await addFilterWithDropdownValue(page, ref, 'created_at', '>=', todayValue)
const rows = page.locator('[role="row"]')
await expect(rows).toHaveCount(2)
} finally {
await dropTable(tableName)
}
})
})
test.describe('Keyboard Navigation - Dropdown Values', () => {
test('ArrowDown/Enter navigates and selects dropdown value option', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_kb_dropdown`
await query(
`CREATE TABLE IF NOT EXISTS ${tableName} (
id bigint generated by default as identity primary key,
is_active boolean
)`
)
await query(`INSERT INTO ${tableName} (is_active) VALUES (true), (false)`)
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await selectColumnFilter(page, 'is_active')
await selectOperator(page, 'is_active', '=')
// Navigate to 'false' option (second item) and select with Enter
await page.keyboard.press('ArrowDown')
await page.keyboard.press('Enter')
await expect(page.getByTestId('filter-condition-is_active')).toBeVisible()
} finally {
await dropTable(tableName)
}
})
test('Tab selects dropdown value option', async ({ page, ref }) => {
const tableName = `${tableNamePrefix}_kb_tab`
await query(
`CREATE TABLE IF NOT EXISTS ${tableName} (
id bigint generated by default as identity primary key,
is_active boolean
)`
)
await query(`INSERT INTO ${tableName} (is_active) VALUES (true), (false)`)
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
await selectColumnFilter(page, 'is_active')
await selectOperator(page, 'is_active', '=')
await page.keyboard.press('Tab')
await expect(page.getByTestId('filter-condition-is_active')).toBeVisible()
} finally {
await dropTable(tableName)
}
})
})
})
+84
View File
@@ -0,0 +1,84 @@
import { expect, Page } from '@playwright/test'
import { createApiResponseWaiter, waitForTableToLoad } from './wait-for-response.js'
const FILTER_BAR_KEY = 'supabase-ui-table-filter-bar'
export async function enableFilterBar(page: Page) {
await page.evaluate((key) => {
localStorage.setItem(key, 'true')
}, FILTER_BAR_KEY)
}
export function getFilterBarInput(page: Page) {
return page.getByTestId('filter-bar-freeform-input')
}
export async function navigateToTable(page: Page, ref: string, tableName: string) {
const gridWaiter = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=table-rows-')
await page.getByRole('button', { name: `View ${tableName}`, exact: true }).click()
await page.waitForURL(/\/editor\/\d+\?schema=public$/)
await gridWaiter
}
export async function selectColumnFilter(page: Page, columnName: string) {
const freeformInput = getFilterBarInput(page)
await freeformInput.click()
await freeformInput.fill(columnName)
await expect(page.getByTestId(`filter-menu-item-${columnName}`)).toBeVisible()
await page.keyboard.press('Enter')
await expect(page.getByTestId(`filter-operator-${columnName}`)).toBeFocused()
}
export async function selectOperator(page: Page, columnName: string, operator: string) {
await expect(page.getByTestId(`filter-menu-item-${operator}`)).toBeVisible()
await page.keyboard.press('Enter')
await expect(page.getByTestId(`filter-value-${columnName}`)).toBeFocused()
}
export async function addFilter(
page: Page,
ref: string,
columnName: string,
operator: string,
value: string
) {
await selectColumnFilter(page, columnName)
await selectOperator(page, columnName, operator)
const rowsWaiter = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=table-rows-')
const valueInput = page.getByTestId(`filter-value-${columnName}`)
await valueInput.fill(value)
await page.keyboard.press('Enter')
await rowsWaiter
}
export async function selectOperatorByClick(page: Page, columnName: string, operator: string) {
await expect(page.getByTestId(`filter-menu-item-${operator}`)).toBeVisible()
await page.getByTestId(`filter-menu-item-${operator}`).click()
await expect(page.getByTestId(`filter-value-${columnName}`)).toBeFocused()
}
export async function addFilterWithDropdownValue(
page: Page,
ref: string,
columnName: string,
operator: string,
optionValue: string
) {
await selectColumnFilter(page, columnName)
await selectOperatorByClick(page, columnName, operator)
const rowsWaiter = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=table-rows-')
await expect(page.getByTestId(`filter-menu-item-${optionValue}`)).toBeVisible()
await page.getByTestId(`filter-menu-item-${optionValue}`).click()
await rowsWaiter
}
export async function setupFilterBarPage(page: Page, ref: string, editorUrl: string) {
const loadPromise = waitForTableToLoad(page, ref)
await page.goto(editorUrl)
await loadPromise
await enableFilterBar(page)
const reloadPromise = waitForTableToLoad(page, ref)
await page.reload({ waitUntil: 'networkidle' })
await reloadPromise
}
@@ -29,6 +29,7 @@ export function CommandListItem({
isHighlighted && 'bg-surface-300',
!isHighlighted && 'hover:bg-surface-200'
)}
data-testid={`filter-menu-item-${item.value}`}
>
<span className="flex items-center gap-2">
{includeIcon && item.icon}
@@ -222,6 +222,8 @@ export function FilterCondition({
variant === 'pill' ? 'rounded border' : 'border-r',
isHighlighted && 'ring-2 ring-primary'
)}
data-testid={`filter-condition-${property.name}`}
data-highlighted={isHighlighted}
>
<span
className="text-xs pl-2 pr-1 cursor-pointer shrink-0 whitespace-nowrap text-foreground-light h-full flex items-center"
@@ -243,6 +245,7 @@ export function FilterCondition({
className="h-full border-none bg-transparent py-0 px-1 text-center text-xs focus:outline-none focus:ring-0 focus:shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 text-brand w-full absolute left-0 top-0"
disabled={isLoading}
aria-label={`Operator for ${property.label}`}
data-testid={`filter-operator-${property.name}`}
/>
<span className="invisible whitespace-pre text-xs block px-1 shrink-0 px-1">
{condition.operator || ' '}
@@ -285,6 +288,7 @@ export function FilterCondition({
className="h-full border-none bg-transparent py-0 px-1 text-xs focus:outline-none focus:ring-0 focus:shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 w-full absolute left-0 top-0"
disabled={isLoading}
aria-label={`Value for ${property.label}`}
data-testid={`filter-value-${property.name}`}
/>
<span className="invisible whitespace-pre text-xs block px-1">{localValue || ' '}</span>
</div>
@@ -343,6 +347,7 @@ export function FilterCondition({
className="group hover:text-foreground hover:!bg-surface-600 rounded-none px-1 h-auto py-0"
aria-label={`Remove ${property.label} filter`}
tabIndex={-1}
data-testid={`filter-remove-${property.name}`}
/>
</div>
)
@@ -195,6 +195,7 @@ export function FilterGroup({ group, path }: FilterGroupProps) {
group.conditions.length === 0 ? emptyPlaceholder : 'Add more filters...'
}
disabled={isLoading}
data-testid="filter-bar-freeform-input"
/>
) : (
<div className="relative inline-block">