Skip to content

Commit 11bc58b

Browse files
authored
feat: add "jump to next block" for code coverage inspector (#32)
1 parent 9057715 commit 11bc58b

File tree

5 files changed

+114
-20
lines changed

5 files changed

+114
-20
lines changed

scripts/analyze-css-coverage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// oxlint-disable max-depth
2+
13
import * as fs from 'node:fs'
24
import * as path from 'node:path'
35
import { calculate_coverage } from '../src/lib/components/coverage/calculate-coverage.ts'

src/lib/components/Icon.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
</script>
4141

4242
<svg width={size} height={size} class={[color, 'icon', classname]} aria-hidden="true" fill-rule="evenodd">
43-
<use xlink:href="#svg--{name}" />
43+
<use href="#svg--{name}" />
4444
</svg>
4545

4646
<style>

src/lib/components/Pre.svelte

Lines changed: 104 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { onMount, onDestroy } from 'svelte'
33
import type { CssLocation } from '$lib/css-location'
44
import { highlight_css } from './use-css-highlight'
5+
import Icon from '$components/Icon.svelte'
56
67
type BaseProps = {
78
css?: string
@@ -125,34 +126,72 @@
125126
126127
let chunks = [
127128
{
128-
start: 0,
129-
covered: line_coverage[0] === 1,
130-
end: 0,
131-
size: 0,
129+
start_line: 0,
130+
is_covered: line_coverage[0] === 1,
131+
end_line: 0,
132+
size: 0
132133
}
133134
]
134135
135136
for (let index = 0; index < line_coverage.length; index++) {
136137
let is_covered = line_coverage[index]
137138
if (index > 0 && is_covered !== line_coverage[index - 1]) {
138139
let last_chunk = chunks.at(-1)!
139-
last_chunk.end = index
140-
last_chunk.size = index - last_chunk.start
140+
last_chunk.end_line = index
141+
last_chunk.size = index - last_chunk.start_line
141142
142143
chunks.push({
143-
start: index,
144-
covered: is_covered === 1,
145-
end: index,
144+
start_line: index,
145+
is_covered: is_covered === 1,
146+
end_line: index,
146147
size: 0
147148
})
148149
}
149150
}
150151
151152
let last_chunk = chunks.at(-1)!
152-
last_chunk.size = line_coverage.length - last_chunk.start
153+
last_chunk.size = line_coverage.length - last_chunk.start_line
153154
154155
return chunks
155156
})
157+
158+
function scroll_to_line(line: number) {
159+
body?.scrollTo({
160+
top: line * LINE_HEIGHT,
161+
behavior: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth'
162+
})
163+
}
164+
165+
function jump_to_next_uncovered() {
166+
if (!line_number_chunks) return
167+
168+
let current_scroll_offset = body?.scrollTop || 0
169+
170+
let next_uncovered_chunk = line_number_chunks.findIndex((chunk) => {
171+
if (chunk.is_covered) return false
172+
let chunk_top = chunk.start_line * LINE_HEIGHT
173+
return chunk_top > current_scroll_offset
174+
})
175+
176+
let next_chunk = line_number_chunks[next_uncovered_chunk] || line_number_chunks.find((chunk) => !chunk.is_covered)
177+
scroll_to_line(next_chunk.start_line)
178+
}
179+
180+
function jump_to_previous_uncovered() {
181+
if (!line_number_chunks) return
182+
183+
let current_scroll_offset = body?.scrollTop || 0
184+
185+
let previous_uncovered_chunk = line_number_chunks.findLastIndex((chunk) => {
186+
if (chunk.is_covered) return false
187+
let chunk_top = chunk.start_line * LINE_HEIGHT
188+
return chunk_top < current_scroll_offset
189+
})
190+
191+
let next_chunk =
192+
line_number_chunks[previous_uncovered_chunk] || line_number_chunks.findLast((chunk) => !chunk.is_covered)
193+
scroll_to_line(next_chunk.start_line)
194+
}
156195
</script>
157196

158197
<!-- TODO: get rid of #key (only needed because of buggy use:highlight_css)
@@ -167,12 +206,28 @@
167206
style:--pre-line-number-width={line_number_width}
168207
style:height="calc({total_lines + 1} * var(--pre-line-height))"
169208
>
209+
{#if show_coverage && line_number_chunks && line_number_chunks.length > 1}
210+
{@const uncovered_blocks_count = line_number_chunks.filter((c) => !c.is_covered).length}
211+
<div class="toolbar">
212+
<p>
213+
{uncovered_blocks_count} un-covered {uncovered_blocks_count === 1 ? 'block' : 'blocks'}
214+
</p>
215+
<button type="button" onclick={jump_to_previous_uncovered} title="Go to the previous un-covered block">
216+
<span class="sr-only">Go to the previous un-covered block</span>
217+
<Icon name="chevron-up" size={12} />
218+
</button>
219+
<button type="button" onclick={jump_to_next_uncovered} title="Go to the next un-covered block">
220+
<span class="sr-only">Go to the next un-covered block</span>
221+
<Icon name="chevron-down" size={12} />
222+
</button>
223+
</div>
224+
{/if}
170225
{#if show_line_numbers}
171226
<div class="line-numbers" aria-hidden="true">
172-
{#if show_coverage === true && line_number_chunks !== undefined}
173-
{#each line_number_chunks as chunk (chunk.start)}
174-
<div class={['line-number-range', { uncovered: !chunk.covered }]}>
175-
{Array.from({ length: chunk.size }, (_, i) => i + 1 + chunk.start)
227+
{#if show_coverage === true && line_number_chunks && line_number_chunks.length > 0}
228+
{#each line_number_chunks as chunk (chunk.start_line)}
229+
<div class={['line-number-range', { uncovered: !chunk.is_covered }]}>
230+
{Array.from({ length: chunk.size }, (_, i) => i + 1 + chunk.start_line)
176231
.join('\n')
177232
.trim()}
178233
</div>
@@ -225,14 +280,48 @@
225280
}
226281
}
227282
228-
& > * {
283+
& .line-numbers,
284+
& pre {
229285
padding-block: var(--space-2);
230286
line-height: var(--pre-line-height);
231287
font-family: var(--font-mono);
232288
font-size: var(--size-specimen);
233289
}
234290
}
235291
292+
.toolbar {
293+
position: sticky;
294+
top: 0;
295+
right: 0;
296+
left: 0;
297+
grid-row: 1 / -1;
298+
grid-column: 1 / -1;
299+
z-index: 1;
300+
background-color: var(--bg-200);
301+
display: flex;
302+
align-items: center;
303+
justify-content: space-between;
304+
gap: var(--space-2);
305+
padding-inline: var(--space-2);
306+
padding-block: var(--space-2);
307+
308+
p {
309+
margin-inline-end: auto;
310+
font-size: var(--size-sm);
311+
}
312+
313+
button {
314+
padding-inline: var(--space-2);
315+
padding-block: var(--space-1);
316+
background-color: transparent;
317+
318+
&:hover,
319+
&:focus {
320+
background-color: var(--bg-400);
321+
}
322+
}
323+
}
324+
236325
.line-numbers {
237326
color: var(--fg-400);
238327
text-align: end;

src/lib/components/coverage/calculate-coverage.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ test.describe('collect coverage', () => {
143143
})
144144

145145
test.describe('calculates coverage', () => {
146-
test.describe('from <style> tag', async () => {
146+
test.describe('from <style> tag', () => {
147147
let coverage: Coverage[]
148148

149149
test.beforeAll(async () => {

src/routes/(public)/design-tokens/spec.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@ test.describe('navigation', () => {
5050

5151
if (section.items !== undefined) {
5252
for (let sub_section of section.items) {
53+
let link = page.getByRole('link', { name: sub_section.title })
5354
let element = page.locator('#' + sub_section.id)
5455
await expect.soft(element, `Expect "#${sub_section.id}" to be on the page`).toHaveCount(1)
56+
await link.click()
57+
await expect.soft(element).toBeInViewport()
5558
}
5659
}
5760
}
@@ -156,7 +159,7 @@ test.describe('URL preloading', () => {
156159

157160
// Select a file
158161
await page.getByRole('tab', { name: 'Analyze File' }).click()
159-
await page.getByLabel('File to analyze').setInputFiles([file_fixture_1]);
162+
await page.getByLabel('File to analyze').setInputFiles([file_fixture_1])
160163
await page.getByRole('button', { name: 'Analyze CSS' }).click()
161164
await expect.soft(page).toHaveURL('/design-tokens')
162165
})
@@ -211,8 +214,8 @@ test.describe('Design Tokens panel', () => {
211214
await button.click()
212215

213216
let clipboard_text = await page.evaluate(async () => {
214-
return await navigator.clipboard.readText();
215-
});
217+
return await navigator.clipboard.readText()
218+
})
216219
expect.soft(clipboard_text).toContain('"com.projectwallace.css-authored-as": "12px"')
217220
})
218221
})

0 commit comments

Comments
 (0)