data table + db manager e2e test

This commit is contained in:
Diego Imbert
2026-01-07 16:40:02 +01:00
parent 544b1fd67f
commit 27a850a388
10 changed files with 119 additions and 27 deletions
+84 -2
View File
@@ -1,6 +1,6 @@
import { test, expect } from '@playwright/test'
import { test, expect, Page } from '@playwright/test'
test('create new data table with custom instance', async ({ page }) => {
async function setupNewDataTable(page: Page): Promise<{ datatableId: string }> {
// Generate unique ID with timestamp
const timestamp = Date.now()
const datatableId = `datatable_${timestamp}`
@@ -67,4 +67,86 @@ test('create new data table with custom instance', async ({ page }) => {
// Verify success toast appears
const saveSuccessToast = page.locator('text=saved successfully')
await expect(saveSuccessToast).toBeVisible({ timeout: 10000 })
return { datatableId }
}
test('create new data table, add a new table, CRUD rows and delete the table', async ({ page }) => {
let { datatableId } = await setupNewDataTable(page)
const table = page.locator('table')
const lastRow = table.locator('tr').nth(-2)
lastRow.locator('button:has-text("Manage")').click()
// Wait for DB Manager drawer to appear
const dbManager = page.locator('#db-manager-drawer')
await expect(dbManager).toBeVisible()
await dbManager.locator('button:has-text("New table")').click()
const tableEditor = page.locator('#db-table-editor-drawer')
await expect(tableEditor).toBeVisible()
const nameInput = page.locator('label:has-text("Name")').locator('input')
await nameInput.fill('friend')
const columnsTable = page.locator('#columns-section table')
const addColumnButton = columnsTable.locator('button:has-text("Add")')
await addColumnButton.click()
const newColRow = columnsTable.locator('tr').nth(-2)
const newColNameInput = newColRow.locator('td').nth(0).locator('input')
await newColNameInput.fill('name')
const newColTypeSelect = newColRow.locator('td').nth(1).locator('input')
await newColTypeSelect.click()
await page.locator('li:has-text("TEXT")').first().click()
await page.locator('button:has-text("Create table")').click()
await page.locator('#db-table-editor-confirmation-modal button:has-text("Create")').click()
// Verify success toast appears
const saveSuccessToast = page.locator('text=friend created!')
await expect(saveSuccessToast).toBeVisible({ timeout: 10000 })
const friendTableKey = page.locator('.db-manager-table-key', { hasText: 'friend' })
await expect(friendTableKey).toBeVisible({ timeout: 10000 })
await friendTableKey.click()
// Add a new row
await dbManager.locator('button:has-text("Insert")').click()
let insertRowDrawer = page.locator('#insert-row-drawer')
await expect(insertRowDrawer).toBeVisible({ timeout: 10000 })
await insertRowDrawer.locator('textarea').fill('Alice', { force: true }) // Not sure why force is needed here
await insertRowDrawer.locator('button:has-text("Insert")').click()
const rowInsertedToast = page.locator('text=Row inserted')
await expect(rowInsertedToast).toBeVisible({ timeout: 10000 })
const insertedRow = dbManager.locator('.ag-cell-value', { hasText: 'Alice' })
await expect(insertedRow).toBeVisible({ timeout: 10000 })
// Edit the row
await insertedRow.dblclick()
const cellEditor = dbManager.locator('.ag-cell-editor input')
await cellEditor.fill('Bob')
await cellEditor.press('Enter')
const rowUpdatedToast = page.locator('text=Value updated')
await expect(rowUpdatedToast).toBeVisible({ timeout: 10000 })
const updatedRow = dbManager.locator('.ag-cell-value', { hasText: 'Bob' })
await expect(updatedRow).toBeVisible({ timeout: 10000 })
const actionsBtn = dbManager.locator('#db-manager-table-actions-friend')
await actionsBtn.click()
await page.locator('button:has-text("Delete table")').click()
let deletePermanentlyBtn = page.locator(
'#db-manager-delete-table-confirmation-modal button:has-text("Delete")'
)
await deletePermanentlyBtn.click()
await expect(page.locator("text=Table 'friend' deleted successfully")).toBeVisible({
timeout: 10000
})
})
+10 -19
View File
@@ -1,14 +1,6 @@
<script lang="ts">
import { type DBSchema } from '$lib/stores'
import {
ChevronDownIcon,
EditIcon,
Loader2,
MoreVertical,
Plus,
Table2,
Trash2Icon
} from 'lucide-svelte'
import { ChevronDownIcon, EditIcon, Loader2, Plus, Table2, Trash2Icon } from 'lucide-svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { ClearableInput, Drawer, DrawerContent } from './common'
import { sendUserToast } from '$lib/toast'
@@ -29,7 +21,7 @@
diffTableEditorValues
} from './apps/components/display/dbtable/queries/alterTable'
import { resource } from 'runed'
import { capitalize, pluralize } from '$lib/utils'
import { capitalize, onlyAlphaNumAndUnderscore, pluralize } from '$lib/utils'
/** Represents a selected table with its schema */
export interface SelectedTable {
@@ -450,7 +442,10 @@
onclick={() => (selected.tableKey = tableKey)}
>
<Table2 class="text-primary shrink-0" size={16} />
<p class="truncate text-ellipsis grow text-left text-emphasis text-xs">{tableKey}</p>
<p
class="db-manager-table-key truncate text-ellipsis grow text-left text-emphasis text-xs"
>{tableKey}</p
>
<DropdownV2
items={() => [
{
@@ -461,6 +456,7 @@
title: `Are you sure you want to delete ${tableKey} ? This action is irreversible`,
confirmationText: 'Delete permanently',
open: true,
id: 'db-manager-delete-table-confirmation-modal',
onConfirm: async () => {
askingForConfirmation && (askingForConfirmation.loading = true)
try {
@@ -488,14 +484,8 @@
}
]}
class="w-fit"
>
<svelte:fragment slot="buttonReplacement">
<MoreVertical
size={8}
class="w-8 h-8 p-2 hover:bg-surface-hover cursor-pointer rounded-md"
/>
</svelte:fragment>
</DropdownV2>
btnId={'db-manager-table-actions-' + onlyAlphaNumAndUnderscore(tableKey)}
/>
</button>
{/each}
{/if}
@@ -537,6 +527,7 @@
on:close={() => (dbTableEditorState = { open: false })}
>
<DrawerContent
id="db-table-editor-drawer"
on:close={() => (dbTableEditorState = { open: false })}
title={dbTableEditorState.alterTableKey
? `Alter ${dbTableEditorState.alterTableKey}`
@@ -108,6 +108,7 @@
}}
CloseIcon={hasReplResult ? ArrowLeft : undefined}
noPadding
id="db-manager-drawer"
>
{#if effectiveInput && $workspaceStore}
{#key selectedDatatable}
@@ -158,7 +158,7 @@
/>
</label>
<div class="flex flex-col">
<div class="flex flex-col" id="columns-section">
<!-- svelte-ignore a11y_label_has_associated_control -->
<label>Columns</label>
<DataTable>
@@ -288,7 +288,7 @@
</DataTable>
</div>
{#if features?.foreignKeys}
<div class="flex flex-col">
<div class="flex flex-col" id="foreign-keys-section">
<!-- svelte-ignore a11y_label_has_associated_control -->
<label>Foreign Keys</label>
<DataTable>
@@ -454,6 +454,7 @@
<Portal>
<ConfirmationModal
id="db-table-editor-confirmation-modal"
{...askingForConfirmation ?? { confirmationText: '', title: '' }}
on:canceled={() => (askingForConfirmation = undefined)}
on:confirmed={askingForConfirmation?.onConfirm ?? (() => {})}
@@ -35,6 +35,7 @@
customWidth?: number | undefined
customMenu?: boolean
class?: string | undefined
btnId?: string | undefined
enableFlyTransition?: boolean
size?: ButtonType.UnifiedSize
btnText?: string
@@ -60,6 +61,7 @@
enableFlyTransition = false,
size = 'md',
btnText = '',
btnId = undefined,
buttonReplacement,
menu,
maxHeight = undefined
@@ -142,6 +144,7 @@
}
}
}}
id={btnId}
data-menu
>
{#if buttonReplacement}
@@ -45,7 +45,7 @@
</Button>
<Drawer bind:this={insertDrawer} size="800px">
<DrawerContent title="Insert row" on:close={insertDrawer.closeDrawer}>
<DrawerContent title="Insert row" on:close={insertDrawer.closeDrawer} id="insert-row-drawer">
{#snippet actions()}
<Button
variant="accent"
@@ -9,10 +9,11 @@
small?: boolean
Icon?: any | undefined
class?: string
id?: string | undefined
onClick?: () => void | undefined | any
}
let { noBg = false, small = false, Icon, class: className, onClick }: Props = $props()
let { noBg = false, small = false, Icon, class: className, id, onClick }: Props = $props()
const dispatch = createEventDispatcher()
</script>
@@ -20,6 +21,7 @@
<Button
on:click={() => (dispatch('close'), onClick?.())}
on:pointerdown={(e) => e.stopPropagation()}
{id}
startIcon={{ icon: Icon ?? X }}
iconOnly
unifiedSize="sm"
@@ -14,6 +14,7 @@
open?: boolean
type?: 'danger' | 'reload'
showIcon?: boolean
id?: string
children?: Snippet
onConfirmed?: () => void | Promise<void>
onCanceled?: () => void
@@ -27,6 +28,7 @@
open = false,
type: _type,
showIcon = true,
id,
children,
onConfirmed,
onCanceled
@@ -84,6 +86,7 @@
transition:fadeFast|local
class={'fixed top-0 bottom-0 left-0 right-0 z-[5000]'}
role="dialog"
{id}
>
<div
class={classNames(
@@ -19,6 +19,7 @@
CloseIcon?: any | undefined
fullScreen?: boolean
eeOnly?: boolean
id?: string | undefined
actions?: import('svelte').Snippet
titleExtra?: import('svelte').Snippet
children?: import('svelte').Snippet
@@ -36,6 +37,7 @@
CloseIcon = undefined,
fullScreen = true,
eeOnly = false,
id,
actions,
titleExtra,
children
@@ -44,7 +46,10 @@
const dispatch = createEventDispatcher()
</script>
<div class={classNames('flex flex-col divide-y', fullScreen ? 'h-screen max-h-screen' : 'h-full')}>
<div
class={classNames('flex flex-col divide-y', fullScreen ? 'h-screen max-h-screen' : 'h-full')}
{id}
>
<div class="flex justify-between w-full items-center pl-2 pr-4 py-2 gap-2">
<div class="flex items-center gap-2 w-full truncate">
<div
@@ -56,7 +61,7 @@
}
}}
>
<CloseButton on:close Icon={CloseIcon} />
<CloseButton on:close Icon={CloseIcon} id="{id}-close-btn" />
</div>
<span class="font-semibold text-emphasis truncate text-lg max-w-sm"
>{title ?? ''}
+4
View File
@@ -1956,3 +1956,7 @@ export function countChars(str: string, char: string): number {
}
return count
}
export function onlyAlphaNumAndUnderscore(str: string): string {
return str.replace(/[^a-zA-Z0-9_]/g, '')
}