Skip to content

Commit 8c0c5f6

Browse files
feat: beta type-checking impl. (#8)
1 parent 5283d66 commit 8c0c5f6

6 files changed

Lines changed: 1051 additions & 26 deletions

File tree

docs/next-steps.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,9 @@ Focused follow-up work for `@knighted/develop`.
2121
5. **In-browser component testing**
2222
- Explore authoring and running component-focused tests in-browser (for example, a Vitest-compatible flow) using CDN-delivered tooling.
2323
- Define a lightweight test UX that supports writing tests, running them on demand, and displaying results in-app.
24+
25+
6. **App runtime modularization**
26+
- 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).
27+
- Preserve `src/app.js` as the main runtime orchestration entrypoint while moving implementation details into focused modules.
28+
- 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.
29+
- Define clear module boundaries and shared interfaces so behavior stays stable while maintainability and readability improve.

playwright/app.spec.ts

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ const waitForInitialRender = async (page: Page) => {
88
await expect(page.locator('#cdn-loading')).toHaveAttribute('hidden', '')
99
}
1010

11+
const setComponentEditorSource = async (page: Page, source: string) => {
12+
const editorContent = page.locator('.component-panel .cm-content').first()
13+
await editorContent.fill(source)
14+
}
15+
16+
const setStylesEditorSource = async (page: Page, source: string) => {
17+
const editorContent = page.locator('.styles-panel .cm-content').first()
18+
await editorContent.fill(source)
19+
}
20+
1121
test('renders default playground preview', async ({ page }) => {
1222
await waitForInitialRender(page)
1323

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

52-
test('shows error status when component source is cleared', async ({ page }) => {
62+
test('transpiles TypeScript annotations in component source', async ({ page }) => {
63+
await waitForInitialRender(page)
64+
65+
await page.getByLabel('ShadowRoot (open)').uncheck()
66+
await setComponentEditorSource(
67+
page,
68+
[
69+
'const Button = ({ label }: { label: string }): unknown => <button>{label}</button>',
70+
'const App = () => <Button label="typed" />',
71+
].join('\n'),
72+
)
73+
74+
await expect(page.locator('#status')).toHaveText('Rendered')
75+
await expect(page.locator('#preview-host button')).toContainText('typed')
76+
})
77+
78+
test('clearing component source reports clear action without error status', async ({
79+
page,
80+
}) => {
5381
await waitForInitialRender(page)
5482

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

88+
await expect(page.locator('#status')).toHaveText('Component cleared')
89+
await expect(page.locator('#status')).toHaveClass(/status--neutral/)
90+
await expect(page.locator('#preview-host pre')).toHaveCount(0)
91+
})
92+
93+
test('jsx syntax errors affect status but not diagnostics toggle severity', async ({
94+
page,
95+
}) => {
96+
await waitForInitialRender(page)
97+
98+
await setComponentEditorSource(
99+
page,
100+
['const App = () => <button', 'const value = 1'].join('\n'),
101+
)
102+
60103
await expect(page.locator('#status')).toHaveText('Error')
61-
await expect(page.locator('#preview-host pre')).toContainText(
62-
'Expected a render() function or a component named App/View.',
104+
await expect(page.locator('#status')).toHaveClass(/status--error/)
105+
await expect(page.locator('#preview-host pre')).toContainText('[jsx]')
106+
await expect(page.locator('#diagnostics-toggle')).toHaveText('Diagnostics')
107+
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
108+
/diagnostics-toggle--neutral/,
63109
)
64110
})
65111

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

177+
test('style compilation errors populate styles diagnostics scope', async ({ page }) => {
178+
await waitForInitialRender(page)
179+
180+
await page.locator('#style-mode').selectOption('sass')
181+
await setStylesEditorSource(page, '.card { color: $missing; }')
182+
183+
await expect(page.locator('#status')).toHaveText('Error')
184+
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
185+
/diagnostics-toggle--error/,
186+
)
187+
188+
await page.locator('#diagnostics-toggle').click()
189+
await expect(page.locator('#diagnostics-styles')).toContainText(
190+
'Style compilation failed.',
191+
)
192+
await expect(page.locator('#diagnostics-styles')).toContainText('Undefined variable')
193+
})
194+
131195
test('clear component action opens confirm dialog and can be canceled', async ({
132196
page,
133197
}) => {
@@ -165,3 +229,37 @@ test('clear styles action opens confirm dialog and clears on confirm', async ({
165229
await expect(cssEditor).toHaveValue('')
166230
await expect(page.locator('#status')).toHaveText('Styles cleared')
167231
})
232+
233+
test('clearing styles keeps diagnostics error state but resets status styling', async ({
234+
page,
235+
}) => {
236+
await waitForInitialRender(page)
237+
238+
await setComponentEditorSource(
239+
page,
240+
["const count: number = 'oops'", 'const App = () => <button>ready</button>'].join(
241+
'\n',
242+
),
243+
)
244+
245+
await page.getByRole('button', { name: 'Typecheck' }).click()
246+
247+
await expect(page.locator('#status')).toHaveText(/Rendered \(Type errors: [1-9]\d*\)/)
248+
await expect(page.locator('#status')).toHaveClass(/status--error/)
249+
await expect(page.locator('#diagnostics-toggle')).toHaveText(/Diagnostics \([1-9]\d*\)/)
250+
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
251+
/diagnostics-toggle--error/,
252+
)
253+
254+
const dialog = page.locator('#clear-confirm-dialog')
255+
await page.getByLabel('Clear styles source').click()
256+
await expect(dialog).toHaveAttribute('open', '')
257+
await dialog.getByRole('button', { name: 'Clear' }).click()
258+
259+
await expect(page.locator('#status')).toHaveText('Styles cleared')
260+
await expect(page.locator('#status')).toHaveClass(/status--neutral/)
261+
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
262+
/diagnostics-toggle--error/,
263+
)
264+
await expect(page.locator('#diagnostics-toggle')).toHaveText(/Diagnostics \([1-9]\d*\)/)
265+
})

0 commit comments

Comments
 (0)