Skip to content

Commit b62a722

Browse files
feat: auto open diagnostics on error. (#20)
1 parent 23c6d67 commit b62a722

5 files changed

Lines changed: 88 additions & 25 deletions

File tree

playwright/app.spec.ts

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,28 @@ const ensurePanelToolsVisible = async (page: Page, panelName: 'component' | 'sty
7474
}
7575
}
7676

77+
const ensureDiagnosticsDrawerOpen = async (page: Page) => {
78+
const toggle = page.locator('#diagnostics-toggle')
79+
const isExpanded = await toggle.getAttribute('aria-expanded')
80+
81+
if (isExpanded !== 'true') {
82+
await toggle.click()
83+
}
84+
85+
await expect(page.locator('#diagnostics-drawer')).toBeVisible()
86+
}
87+
88+
const ensureDiagnosticsDrawerClosed = async (page: Page) => {
89+
const toggle = page.locator('#diagnostics-toggle')
90+
const isExpanded = await toggle.getAttribute('aria-expanded')
91+
92+
if (isExpanded === 'true') {
93+
await page.locator('#diagnostics-close').click()
94+
}
95+
96+
await expect(page.locator('#diagnostics-drawer')).toBeHidden()
97+
}
98+
7799
const expectCollapseButtonState = async (
78100
page: Page,
79101
panelName: 'component' | 'styles' | 'preview',
@@ -383,7 +405,7 @@ test('react mode typecheck loads types without malformed URL fetches', async ({
383405
await page.locator('#render-mode').selectOption('react')
384406
await page.getByRole('button', { name: 'Typecheck' }).click()
385407

386-
await page.locator('#diagnostics-toggle').click()
408+
await ensureDiagnosticsDrawerOpen(page)
387409
await expect(page.locator('#diagnostics-component')).toContainText(
388410
'No TypeScript errors found.',
389411
)
@@ -558,7 +580,7 @@ test('style compilation errors populate styles diagnostics scope', async ({ page
558580
/diagnostics-toggle--error/,
559581
)
560582

561-
await page.locator('#diagnostics-toggle').click()
583+
await ensureDiagnosticsDrawerOpen(page)
562584
await expect(page.locator('#diagnostics-styles')).toContainText(
563585
'Style compilation failed.',
564586
)
@@ -627,6 +649,7 @@ test('clearing styles keeps diagnostics error state but resets status styling',
627649
)
628650

629651
const dialog = page.locator('#clear-confirm-dialog')
652+
await ensureDiagnosticsDrawerClosed(page)
630653
await page.getByLabel('Clear styles source').click()
631654
await expect(dialog).toHaveAttribute('open', '')
632655
await dialog.getByRole('button', { name: 'Clear' }).click()
@@ -659,7 +682,7 @@ test('clear component diagnostics removes type errors and restores rendered stat
659682
)
660683
await expect(page.locator('#status')).toHaveText(/Rendered \(Type errors: [1-9]\d*\)/)
661684

662-
await page.locator('#diagnostics-toggle').click()
685+
await ensureDiagnosticsDrawerOpen(page)
663686
await page.locator('#diagnostics-clear-component').click()
664687

665688
await expect(page.locator('#diagnostics-component')).toContainText(
@@ -685,7 +708,7 @@ test('clear all diagnostics removes style compile diagnostics', async ({ page })
685708
/diagnostics-toggle--error/,
686709
)
687710

688-
await page.locator('#diagnostics-toggle').click()
711+
await ensureDiagnosticsDrawerOpen(page)
689712
await expect(page.locator('#diagnostics-styles')).toContainText(
690713
'Style compilation failed.',
691714
)
@@ -713,7 +736,7 @@ test('clear styles diagnostics removes style compile diagnostics', async ({ page
713736
/diagnostics-toggle--error/,
714737
)
715738

716-
await page.locator('#diagnostics-toggle').click()
739+
await ensureDiagnosticsDrawerOpen(page)
717740
await expect(page.locator('#diagnostics-styles')).toContainText(
718741
'Style compilation failed.',
719742
)
@@ -740,7 +763,7 @@ test('typecheck success reports ok diagnostics state in button and drawer', asyn
740763
await expect(page.locator('#diagnostics-toggle')).toHaveClass(/diagnostics-toggle--ok/)
741764
await expect(page.locator('#diagnostics-toggle')).toHaveText('Diagnostics')
742765

743-
await page.locator('#diagnostics-toggle').click()
766+
await ensureDiagnosticsDrawerOpen(page)
744767
await expect(page.locator('#diagnostics-component')).toContainText(
745768
'No TypeScript errors found.',
746769
)
@@ -766,7 +789,7 @@ test('typecheck error reports diagnostics count in button and details in drawer'
766789
)
767790
await expect(page.locator('#diagnostics-toggle')).toHaveText(/Diagnostics \([1-9]\d*\)/)
768791

769-
await page.locator('#diagnostics-toggle').click()
792+
await expect(page.locator('#diagnostics-drawer')).toBeVisible()
770793
await expect(page.locator('#diagnostics-component')).toContainText('TypeScript found')
771794
await expect(page.locator('#diagnostics-component')).toContainText('TS')
772795
})
@@ -787,7 +810,7 @@ test('component diagnostics rows navigate editor to reported line', async ({ pag
787810
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
788811
/diagnostics-toggle--error/,
789812
)
790-
await page.locator('#diagnostics-toggle').click()
813+
await ensureDiagnosticsDrawerOpen(page)
791814

792815
const targetDiagnostic = page
793816
.locator('#diagnostics-component .diagnostic-line-button[data-diagnostic-line="2"]')
@@ -817,7 +840,7 @@ test('component diagnostics support arrow navigation and enter jump', async ({
817840
/diagnostics-toggle--error/,
818841
)
819842

820-
await page.locator('#diagnostics-toggle').click()
843+
await ensureDiagnosticsDrawerOpen(page)
821844

822845
const firstDiagnostic = page
823846
.locator('#diagnostics-component .diagnostic-line-button')
@@ -854,7 +877,7 @@ test('component lint error reports diagnostics count and details', async ({ page
854877
)
855878
await expect(page.locator('#diagnostics-toggle')).toHaveText(/Diagnostics \([1-9]\d*\)/)
856879

857-
await page.locator('#diagnostics-toggle').click()
880+
await expect(page.locator('#diagnostics-drawer')).toBeVisible()
858881
await expect(page.locator('#diagnostics-component')).toContainText(
859882
'Biome reported issues.',
860883
)
@@ -874,7 +897,7 @@ test('styles diagnostics rows navigate editor to reported line', async ({ page }
874897
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
875898
/diagnostics-toggle--error/,
876899
)
877-
await page.locator('#diagnostics-toggle').click()
900+
await ensureDiagnosticsDrawerOpen(page)
878901

879902
const targetDiagnostic = page
880903
.locator('#diagnostics-styles .diagnostic-line-button[data-diagnostic-line="3"]')
@@ -904,7 +927,7 @@ test('clear component diagnostics resets rendered lint-issue status pill', async
904927
await expect(page.locator('#status')).toHaveText(/Rendered \(Lint issues: [1-9]\d*\)/)
905928
await expect(page.locator('#status')).toHaveClass(/status--error/)
906929

907-
await page.locator('#diagnostics-toggle').click()
930+
await ensureDiagnosticsDrawerOpen(page)
908931
await page.locator('#diagnostics-clear-component').click()
909932

910933
await expect(page.locator('#diagnostics-component')).toContainText(
@@ -932,7 +955,7 @@ test('component lint ignores unused App View and render bindings', async ({ page
932955

933956
await runComponentLint(page)
934957

935-
await page.locator('#diagnostics-toggle').click()
958+
await ensureDiagnosticsDrawerOpen(page)
936959
await expect(page.locator('#diagnostics-component')).toContainText(
937960
'No Biome issues found.',
938961
)
@@ -1018,7 +1041,7 @@ test('changing css dialect resets diagnostics after lint and typecheck runs', as
10181041
)
10191042
await expect(page.locator('#diagnostics-toggle')).toHaveText('Diagnostics')
10201043

1021-
await page.locator('#diagnostics-toggle').click()
1044+
await ensureDiagnosticsDrawerOpen(page)
10221045
await expect(page.locator('#diagnostics-component')).toContainText(
10231046
'No diagnostics yet.',
10241047
)

src/app.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,11 @@ const typeDiagnostics = createTypeDiagnosticsController({
504504
incrementTypeDiagnosticsRuns,
505505
decrementTypeDiagnosticsRuns,
506506
getActiveTypeDiagnosticsRuns,
507+
onIssuesDetected: ({ issueCount }) => {
508+
if (issueCount > 0) {
509+
setDiagnosticsDrawerOpen(true)
510+
}
511+
},
507512
})
508513

509514
const lintDiagnostics = createLintDiagnosticsController({
@@ -515,6 +520,11 @@ const lintDiagnostics = createLintDiagnosticsController({
515520
setComponentDiagnostics: setTypeDiagnosticsDetails,
516521
setStyleDiagnostics: setStyleDiagnosticsDetails,
517522
setStatus,
523+
onIssuesDetected: ({ issueCount }) => {
524+
if (issueCount > 0) {
525+
setDiagnosticsDrawerOpen(true)
526+
}
527+
},
518528
})
519529

520530
let activeComponentLintAbortController = null
@@ -544,7 +554,7 @@ const syncLintPendingState = () => {
544554
setLintDiagnosticsPending(componentLintPending || stylesLintPending)
545555
}
546556

547-
const runComponentLint = async () => {
557+
const runComponentLint = async ({ userInitiated = false } = {}) => {
548558
activeComponentLintAbortController?.abort()
549559
const controller = new AbortController()
550560
activeComponentLintAbortController = controller
@@ -557,6 +567,7 @@ const runComponentLint = async () => {
557567
try {
558568
const result = await lintDiagnostics.lintComponent({
559569
signal: controller.signal,
570+
userInitiated,
560571
})
561572
if (result) {
562573
lastComponentLintIssueCount = result.issueCount
@@ -570,7 +581,7 @@ const runComponentLint = async () => {
570581
}
571582
}
572583

573-
const runStylesLint = async () => {
584+
const runStylesLint = async ({ userInitiated = false } = {}) => {
574585
activeStylesLintAbortController?.abort()
575586
const controller = new AbortController()
576587
activeStylesLintAbortController = controller
@@ -583,6 +594,7 @@ const runStylesLint = async () => {
583594
try {
584595
const result = await lintDiagnostics.lintStyles({
585596
signal: controller.signal,
597+
userInitiated,
586598
})
587599
if (result) {
588600
lastStylesLintIssueCount = result.issueCount
@@ -901,17 +913,17 @@ if (diagnosticsClearAll) {
901913
}
902914
if (typecheckButton) {
903915
typecheckButton.addEventListener('click', () => {
904-
typeDiagnostics.triggerTypeDiagnostics()
916+
typeDiagnostics.triggerTypeDiagnostics({ userInitiated: true })
905917
})
906918
}
907919
if (lintComponentButton) {
908920
lintComponentButton.addEventListener('click', () => {
909-
void runComponentLint()
921+
void runComponentLint({ userInitiated: true })
910922
})
911923
}
912924
if (lintStylesButton) {
913925
lintStylesButton.addEventListener('click', () => {
914-
void runStylesLint()
926+
void runStylesLint({ userInitiated: true })
915927
})
916928
}
917929
renderButton.addEventListener('click', renderPreview)

src/modules/lint-diagnostics.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ export const createLintDiagnosticsController = ({
210210
setComponentDiagnostics,
211211
setStyleDiagnostics,
212212
setStatus,
213+
onIssuesDetected = () => {},
213214
}) => {
214215
let biomeWorkspacePromise = null
215216
let componentLintRunId = 0
@@ -296,7 +297,7 @@ export const createLintDiagnosticsController = ({
296297
}
297298
}
298299

299-
const lintComponent = async ({ signal } = {}) => {
300+
const lintComponent = async ({ signal, userInitiated = false } = {}) => {
300301
componentLintRunId += 1
301302
const runId = componentLintRunId
302303

@@ -331,6 +332,15 @@ export const createLintDiagnosticsController = ({
331332
: 'Rendered',
332333
summary.level === 'error' ? 'error' : 'neutral',
333334
)
335+
336+
if (userInitiated && summary.lines.length > 0) {
337+
onIssuesDetected({
338+
kind: 'lint',
339+
scope: 'component',
340+
issueCount: summary.lines.length,
341+
})
342+
}
343+
334344
return {
335345
issueCount: summary.lines.length,
336346
}
@@ -356,7 +366,7 @@ export const createLintDiagnosticsController = ({
356366
}
357367
}
358368

359-
const lintStyles = async ({ signal } = {}) => {
369+
const lintStyles = async ({ signal, userInitiated = false } = {}) => {
360370
stylesLintRunId += 1
361371
const runId = stylesLintRunId
362372

@@ -403,6 +413,15 @@ export const createLintDiagnosticsController = ({
403413
: 'Rendered',
404414
summary.level === 'error' ? 'error' : 'neutral',
405415
)
416+
417+
if (userInitiated && summary.lines.length > 0) {
418+
onIssuesDetected({
419+
kind: 'lint',
420+
scope: 'styles',
421+
issueCount: summary.lines.length,
422+
})
423+
}
424+
406425
return {
407426
issueCount: summary.lines.length,
408427
}

src/modules/type-diagnostics.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ export const createTypeDiagnosticsController = ({
230230
incrementTypeDiagnosticsRuns,
231231
decrementTypeDiagnosticsRuns,
232232
getActiveTypeDiagnosticsRuns,
233+
onIssuesDetected = () => {},
233234
}) => {
234235
let typeCheckRunId = 0
235236
let typeScriptCompiler = null
@@ -840,7 +841,7 @@ export const createTypeDiagnosticsController = ({
840841
.filter(diagnostic => !shouldIgnoreTypeDiagnostic(diagnostic))
841842
}
842843

843-
const runTypeDiagnostics = async runId => {
844+
const runTypeDiagnostics = async (runId, { userInitiated = false } = {}) => {
844845
incrementTypeDiagnosticsRuns()
845846
setTypeDiagnosticsPending(false)
846847
setTypecheckButtonLoading(true)
@@ -879,6 +880,14 @@ export const createTypeDiagnosticsController = ({
879880
level: 'error',
880881
})
881882
setStatus(`Rendered (Type errors: ${errors.length})`, 'error')
883+
884+
if (userInitiated) {
885+
onIssuesDetected({
886+
kind: 'type',
887+
scope: 'component',
888+
issueCount: errors.length,
889+
})
890+
}
882891
}
883892

884893
if (isRenderedStatus()) {
@@ -911,9 +920,9 @@ export const createTypeDiagnosticsController = ({
911920
}
912921
}
913922

914-
const triggerTypeDiagnostics = () => {
923+
const triggerTypeDiagnostics = ({ userInitiated = false } = {}) => {
915924
typeCheckRunId += 1
916-
void runTypeDiagnostics(typeCheckRunId)
925+
void runTypeDiagnostics(typeCheckRunId, { userInitiated })
917926
}
918927

919928
const scheduleTypeRecheck = () => {

src/styles/diagnostics.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@
218218
top: auto;
219219
right: 12px;
220220
left: 12px;
221-
bottom: 68px;
221+
bottom: 12px;
222222
width: auto;
223223
max-height: 58vh;
224224
}

0 commit comments

Comments
 (0)