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
6 changes: 6 additions & 0 deletions docs/next-steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ Focused follow-up work for `@knighted/develop`.
5. **In-browser component testing**
- Explore authoring and running component-focused tests in-browser (for example, a Vitest-compatible flow) using CDN-delivered tooling.
- Define a lightweight test UX that supports writing tests, running them on demand, and displaying results in-app.

6. **App runtime modularization**
- Plan a refactor that splits `src/app.js` into scoped modules organized by functionality (for example: diagnostics, render pipeline, editor integration, UI controls, and persistence).
- Preserve `src/app.js` as the main runtime orchestration entrypoint while moving implementation details into focused modules.
- Split stylesheet concerns into focused files (for example: layout/shell, panel controls, diagnostics, editor overrides, dialogs/overlays) while keeping `src/styles.css` as the single entrypoint via ordered `@import` directives.
- Define clear module boundaries and shared interfaces so behavior stays stable while maintainability and readability improve.
104 changes: 101 additions & 3 deletions playwright/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ const waitForInitialRender = async (page: Page) => {
await expect(page.locator('#cdn-loading')).toHaveAttribute('hidden', '')
}

const setComponentEditorSource = async (page: Page, source: string) => {
const editorContent = page.locator('.component-panel .cm-content').first()
await editorContent.fill(source)
}

const setStylesEditorSource = async (page: Page, source: string) => {
const editorContent = page.locator('.styles-panel .cm-content').first()
await editorContent.fill(source)
}

test('renders default playground preview', async ({ page }) => {
await waitForInitialRender(page)

Expand Down Expand Up @@ -49,17 +59,53 @@ test('renders in react mode with css modules', async ({ page }) => {
await expect(previewItems.first()).toContainText('apple')
})

test('shows error status when component source is cleared', async ({ page }) => {
test('transpiles TypeScript annotations in component source', async ({ page }) => {
await waitForInitialRender(page)

await page.getByLabel('ShadowRoot (open)').uncheck()
await setComponentEditorSource(
page,
[
'const Button = ({ label }: { label: string }): unknown => <button>{label}</button>',
'const App = () => <Button label="typed" />',
].join('\n'),
)

await expect(page.locator('#status')).toHaveText('Rendered')
await expect(page.locator('#preview-host button')).toContainText('typed')
})

test('clearing component source reports clear action without error status', async ({
page,
}) => {
await waitForInitialRender(page)

const dialog = page.locator('#clear-confirm-dialog')
await page.getByLabel('Clear component source').click()
await expect(dialog).toHaveAttribute('open', '')
await dialog.getByRole('button', { name: 'Clear' }).click()

await expect(page.locator('#status')).toHaveText('Component cleared')
await expect(page.locator('#status')).toHaveClass(/status--neutral/)
await expect(page.locator('#preview-host pre')).toHaveCount(0)
})

test('jsx syntax errors affect status but not diagnostics toggle severity', async ({
page,
}) => {
await waitForInitialRender(page)

await setComponentEditorSource(
page,
['const App = () => <button', 'const value = 1'].join('\n'),
)

await expect(page.locator('#status')).toHaveText('Error')
await expect(page.locator('#preview-host pre')).toContainText(
'Expected a render() function or a component named App/View.',
await expect(page.locator('#status')).toHaveClass(/status--error/)
await expect(page.locator('#preview-host pre')).toContainText('[jsx]')
await expect(page.locator('#diagnostics-toggle')).toHaveText('Diagnostics')
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
/diagnostics-toggle--neutral/,
)
})

Expand Down Expand Up @@ -128,6 +174,24 @@ test('renders with sass style mode', async ({ page }) => {
await expect(previewItems.first()).toContainText('apple')
})

test('style compilation errors populate styles diagnostics scope', async ({ page }) => {
await waitForInitialRender(page)

await page.locator('#style-mode').selectOption('sass')
await setStylesEditorSource(page, '.card { color: $missing; }')

await expect(page.locator('#status')).toHaveText('Error')
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
/diagnostics-toggle--error/,
)

await page.locator('#diagnostics-toggle').click()
await expect(page.locator('#diagnostics-styles')).toContainText(
'Style compilation failed.',
)
await expect(page.locator('#diagnostics-styles')).toContainText('Undefined variable')
})

test('clear component action opens confirm dialog and can be canceled', async ({
page,
}) => {
Expand Down Expand Up @@ -165,3 +229,37 @@ test('clear styles action opens confirm dialog and clears on confirm', async ({
await expect(cssEditor).toHaveValue('')
await expect(page.locator('#status')).toHaveText('Styles cleared')
})

test('clearing styles keeps diagnostics error state but resets status styling', async ({
page,
}) => {
await waitForInitialRender(page)

await setComponentEditorSource(
page,
["const count: number = 'oops'", 'const App = () => <button>ready</button>'].join(
'\n',
),
)

await page.getByRole('button', { name: 'Typecheck' }).click()

await expect(page.locator('#status')).toHaveText(/Rendered \(Type errors: [1-9]\d*\)/)
await expect(page.locator('#status')).toHaveClass(/status--error/)
await expect(page.locator('#diagnostics-toggle')).toHaveText(/Diagnostics \([1-9]\d*\)/)
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
/diagnostics-toggle--error/,
)

const dialog = page.locator('#clear-confirm-dialog')
await page.getByLabel('Clear styles source').click()
await expect(dialog).toHaveAttribute('open', '')
await dialog.getByRole('button', { name: 'Clear' }).click()

await expect(page.locator('#status')).toHaveText('Styles cleared')
await expect(page.locator('#status')).toHaveClass(/status--neutral/)
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
/diagnostics-toggle--error/,
)
await expect(page.locator('#diagnostics-toggle')).toHaveText(/Diagnostics \([1-9]\d*\)/)
})
Loading
Loading