diff --git a/tests/playwright/handler-responses.spec.js b/tests/playwright/handler-responses.spec.js new file mode 100644 index 0000000..2928580 --- /dev/null +++ b/tests/playwright/handler-responses.spec.js @@ -0,0 +1,140 @@ +const { test, expect } = require('playwright/test'); +const BASE_URL = process.env.SX_TEST_URL || 'http://localhost:8013'; + +test.describe('Handler responses render correctly', () => { + + test('bulk-update: deactivate renders proper HTML attributes', async ({ page }) => { + await page.goto(BASE_URL + '/sx/(geography.(hypermedia.(example.bulk-update)))', { waitUntil: 'networkidle' }); + await page.waitForTimeout(2000); + + // Check first row, note its status + const firstCheckbox = page.locator('input[type="checkbox"]').first(); + await firstCheckbox.check(); + + // Click Deactivate + await page.locator('button:has-text("Deactivate")').first().click(); + await page.waitForTimeout(3000); + + // The table should still have proper HTML — no raw "class" text visible + const tableText = await page.locator('table').first().textContent(); + expect(tableText).not.toContain('classpx'); + expect(tableText).not.toContain('classborder'); + + // Rows should have proper class attributes, not class as text content + const firstTd = page.locator('table tbody tr td').first(); + const tdClass = await firstTd.getAttribute('class'); + expect(tdClass).toBeTruthy(); + expect(tdClass).toContain('px-'); + }); + + test('delete-row: deleted row disappears with proper rendering', async ({ page }) => { + await page.goto(BASE_URL + '/sx/(geography.(hypermedia.(example.delete-row)))', { waitUntil: 'networkidle' }); + await page.waitForTimeout(2000); + + const rows = page.locator('#delete-rows tr, table tbody tr'); + const countBefore = await rows.count(); + + await page.locator('button:has-text("Delete")').first().click(); + await page.waitForTimeout(3000); + + // Row should be removed + const countAfter = await rows.count(); + expect(countAfter).toBeLessThan(countBefore); + + // Remaining rows should have proper class attrs, no raw text + if (countAfter > 0) { + const td = page.locator('table tbody tr td').first(); + const text = await td.textContent(); + expect(text).not.toContain('classpx'); + } + }); + + test('click-to-load: loaded rows have proper HTML', async ({ page }) => { + await page.goto(BASE_URL + '/sx/(geography.(hypermedia.(example.click-to-load)))', { waitUntil: 'networkidle' }); + await page.waitForTimeout(2000); + + const rowsBefore = await page.locator('table tbody tr').count(); + const loadBtn = page.locator('button:has-text("Load More")').first(); + if (await loadBtn.count() > 0) { + await loadBtn.click(); + await page.waitForTimeout(3000); + + const rowsAfter = await page.locator('table tbody tr').count(); + expect(rowsAfter).toBeGreaterThan(rowsBefore); + + // New rows should have proper class attrs + const lastTd = page.locator('table tbody tr:last-child td').first(); + const text = await lastTd.textContent(); + expect(text).not.toContain('classpx'); + } + }); + + test('active-search: results render as proper HTML', async ({ page }) => { + await page.goto(BASE_URL + '/sx/(geography.(hypermedia.(example.active-search)))', { waitUntil: 'networkidle' }); + await page.waitForTimeout(2000); + + const input = page.locator('input[placeholder*="earch"], input[name="q"]').first(); + await input.fill('python'); + await page.waitForTimeout(2000); + + const results = page.locator('#search-results'); + const text = await results.textContent(); + // Should contain search results, not raw SX class text + expect(text).not.toContain('classpx'); + expect(text).not.toContain('classbg'); + expect(text.toLowerCase()).toContain('python'); + }); + + test('form-submission: response renders as HTML not SX text', async ({ page }) => { + await page.goto(BASE_URL + '/sx/(geography.(hypermedia.(example.form-submission)))', { waitUntil: 'networkidle' }); + await page.waitForTimeout(2000); + + const form = page.locator('form').first(); + const inputs = form.locator('input[type="text"], input:not([type])'); + if (await inputs.count() > 0) { + await inputs.first().fill('test-value'); + } + + const submit = form.locator('button[type="submit"], button').first(); + await submit.click(); + await page.waitForTimeout(3000); + + // Response should not have raw SX class text + const root = page.locator('#sx-root'); + const text = await root.textContent(); + expect(text).not.toContain('classpx'); + expect(text).not.toContain('classborder'); + }); + + test('edit-row: edited row renders with proper classes', async ({ page }) => { + await page.goto(BASE_URL + '/sx/(geography.(hypermedia.(example.edit-row)))', { waitUntil: 'networkidle' }); + await page.waitForTimeout(2000); + + const editBtn = page.locator('button:has-text("Edit")').first(); + if (await editBtn.count() > 0) { + await editBtn.click(); + await page.waitForTimeout(2000); + + // Edit form or inline edit should not show raw class text + const root = page.locator('#sx-root'); + const text = await root.textContent(); + expect(text).not.toContain('classpx'); + } + }); + + test('tabs: tab content renders as proper HTML', async ({ page }) => { + await page.goto(BASE_URL + '/sx/(geography.(hypermedia.(example.tabs)))', { waitUntil: 'networkidle' }); + await page.waitForTimeout(2000); + + // Click a tab + const tabs = page.locator('[sx-get*="tab"]'); + if (await tabs.count() >= 2) { + await tabs.nth(1).click(); + await page.waitForTimeout(2000); + + const root = page.locator('#sx-root'); + const text = await root.textContent(); + expect(text).not.toContain('classpx'); + } + }); +});