Skip to content

Commit ff5201f

Browse files
ljodeaclaudegka
authored
docs: add JSDoc and tests for hooks (svelteplot#368)
## Summary - Add JSDoc documentation to all exported functions and classes in `plotDefaults.ts` and `usePlot.svelte.ts` - Add 8 unit tests covering `setPlotDefaults`/`getPlotDefaults` (context merging, override behavior) and `usePlot`/`setPlot` (state access via Plot component context) - Test wrapper components follow the existing pattern from `src/tests/plot.test.svelte` ## Test plan - [x] All 8 new hook tests pass (`pnpm run test:unit`) - [x] All 451 existing tests still pass - [x] Prettier and ESLint pass (`pnpm run lint`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Gregor Aisch <[email protected]>
1 parent 09d7438 commit ff5201f

File tree

8 files changed

+163
-5
lines changed

8 files changed

+163
-5
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script lang="ts">
2+
import { setPlotDefaults, getPlotDefaults } from './plotDefaults.js';
3+
import type { PlotDefaults } from 'svelteplot/types';
4+
5+
let {
6+
defaults = {}
7+
}: {
8+
defaults?: Partial<PlotDefaults>;
9+
} = $props();
10+
11+
setPlotDefaults(defaults);
12+
13+
const result = getPlotDefaults();
14+
</script>
15+
16+
<div data-testid="defaults" data-defaults={JSON.stringify(result)}></div>

src/lib/hooks/plotDefaults.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { render } from '@testing-library/svelte';
3+
import PlotDefaultsTest from './plotDefaults.test.svelte';
4+
import PlotDefaultsNestedTest from './plotDefaultsNested.test.svelte';
5+
6+
function getDefaults(container: HTMLElement) {
7+
const el = container.querySelector('[data-testid="defaults"]')!;
8+
return JSON.parse(el.getAttribute('data-defaults')!);
9+
}
10+
11+
describe('plotDefaults', () => {
12+
it('getPlotDefaults returns empty object when no defaults set', () => {
13+
const { container } = render(PlotDefaultsTest, {
14+
props: { defaults: {} }
15+
});
16+
expect(getDefaults(container)).toEqual({});
17+
});
18+
19+
it('setPlotDefaults makes values available via getPlotDefaults', () => {
20+
const { container } = render(PlotDefaultsTest, {
21+
props: { defaults: { height: 200 } }
22+
});
23+
expect(getDefaults(container)).toEqual({
24+
height: 200
25+
});
26+
});
27+
28+
it('setPlotDefaults merges with existing defaults', () => {
29+
const { container } = render(PlotDefaultsNestedTest, {
30+
props: {
31+
parentDefaults: { height: 200 },
32+
childDefaults: { inset: 5 }
33+
}
34+
});
35+
expect(getDefaults(container)).toEqual({
36+
height: 200,
37+
inset: 5
38+
});
39+
});
40+
41+
it('later defaults override earlier ones for same key', () => {
42+
const { container } = render(PlotDefaultsNestedTest, {
43+
props: {
44+
parentDefaults: { height: 200 },
45+
childDefaults: { height: 300 }
46+
}
47+
});
48+
expect(getDefaults(container)).toEqual({
49+
height: 300
50+
});
51+
});
52+
});

src/lib/hooks/plotDefaults.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import type { PlotDefaults } from 'svelteplot/types';
33

44
const PLOT_DEFAULTS_KEY = Symbol('svelteplot/defaults');
55

6+
/** sets default options for all Plot components in this component tree, merging with any existing defaults from parent contexts */
67
export function setPlotDefaults(plotDefaults: Partial<PlotDefaults>) {
78
const existingDefaults = getPlotDefaults();
89
const mergedDefaults = { ...existingDefaults, ...plotDefaults };
910
setContext(PLOT_DEFAULTS_KEY, mergedDefaults);
1011
}
1112

13+
/** retrieves the current plot defaults from the Svelte context, or an empty object if none have been set */
1214
export function getPlotDefaults(): Partial<PlotDefaults> {
1315
return hasContext(PLOT_DEFAULTS_KEY)
1416
? getContext<PlotDefaults>(PLOT_DEFAULTS_KEY)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script lang="ts">
2+
import { setPlotDefaults, getPlotDefaults } from './plotDefaults.js';
3+
import type { PlotDefaults } from 'svelteplot/types';
4+
5+
let {
6+
parentDefaults = {},
7+
childDefaults = {}
8+
}: {
9+
parentDefaults?: Partial<PlotDefaults>;
10+
childDefaults?: Partial<PlotDefaults>;
11+
} = $props();
12+
13+
setPlotDefaults(parentDefaults);
14+
setPlotDefaults(childDefaults);
15+
16+
const result = getPlotDefaults();
17+
</script>
18+
19+
<div data-testid="defaults" data-defaults={JSON.stringify(result)}></div>

src/lib/hooks/usePlot.svelte.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ import type { PlotContext, PlotOptions } from 'svelteplot/types/plot';
33
import type { PlotScales, PlotState as TPlotState } from 'svelteplot/types';
44

55
/**
6-
* Internal state representation of a Plot.
6+
* internal state representation of a Plot, using Svelte 5 runes for reactivity
77
*/
88
class PlotState implements TPlotState {
9-
// Define properties and methods for PlotState as needed
109
width: number = $state(50);
1110
height: number = $state(50);
1211
options: PlotOptions = $state({});
@@ -32,18 +31,18 @@ class PlotState implements TPlotState {
3231
Object.assign(this, state);
3332
}
3433

34+
/** merges partial state into the current plot state */
3535
update(newState: Partial<PlotState>) {
3636
Object.assign(this, newState);
3737
}
3838

39+
/** returns a read-only wrapper exposing only public properties */
3940
get publicState() {
4041
return new PublicPlotState(this);
4142
}
4243
}
4344

44-
/**
45-
* A public-facing wrapper around PlotState that exposes only read-only properties.
46-
*/
45+
/** read-only wrapper around PlotState that exposes only getter properties */
4746
class PublicPlotState {
4847
#plotState: PlotState;
4948
constructor(plotState: PlotState) {
@@ -88,12 +87,14 @@ class PublicPlotState {
8887
}
8988
}
9089

90+
/** creates a new PlotState instance from the given initial state */
9191
export function setPlot(initialState: PlotState): PlotState {
9292
return new PlotState({
9393
...initialState
9494
});
9595
}
9696

97+
/** returns the current Plot's public state from Svelte context. Must be called within a `<Plot>` component tree. */
9798
export function usePlot(): Readonly<TPlotState> {
9899
const { getPlotState } = getContext<PlotContext>('svelteplot');
99100
return getPlotState().publicState;

src/lib/hooks/usePlot.test.svelte

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script lang="ts">
2+
import { Plot } from '$lib/index.js';
3+
import UsePlotChild from './usePlotChild.test.svelte';
4+
5+
let { width = 100, height = 100 }: { width?: number; height?: number } = $props();
6+
</script>
7+
8+
<Plot {width} {height}>
9+
<UsePlotChild />
10+
</Plot>

src/lib/hooks/usePlot.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { render } from '@testing-library/svelte';
3+
import UsePlotTest from './usePlot.test.svelte';
4+
5+
describe('usePlot', () => {
6+
it('returns plot state with correct height', () => {
7+
const { container } = render(UsePlotTest, {
8+
props: { width: 200, height: 150 }
9+
});
10+
const el = container.querySelector('[data-testid="plot-state"]')!;
11+
expect(el.getAttribute('data-height')).toBe('150');
12+
});
13+
14+
it('returns default dimensions when no props given', () => {
15+
const { container } = render(UsePlotTest);
16+
const el = container.querySelector('[data-testid="plot-state"]')!;
17+
expect(el.getAttribute('data-height')).toBe('100');
18+
});
19+
20+
it('exposes plotWidth and plotHeight', () => {
21+
const { container } = render(UsePlotTest, {
22+
props: { width: 100, height: 100 }
23+
});
24+
const el = container.querySelector('[data-testid="plot-state"]')!;
25+
// plotWidth/plotHeight are width/height minus margins
26+
const plotWidth = Number(el.getAttribute('data-plot-width'));
27+
const plotHeight = Number(el.getAttribute('data-plot-height'));
28+
expect(plotWidth).toBeGreaterThan(0);
29+
expect(plotWidth).toBeLessThanOrEqual(100);
30+
expect(plotHeight).toBeGreaterThan(0);
31+
expect(plotHeight).toBeLessThanOrEqual(100);
32+
});
33+
});
34+
35+
describe('setPlot', () => {
36+
it('creates PlotState used by Plot component', () => {
37+
// setPlot is called internally by Plot, so we verify
38+
// it works indirectly through usePlot
39+
const { container } = render(UsePlotTest, {
40+
props: { width: 300, height: 250 }
41+
});
42+
const el = container.querySelector('[data-testid="plot-state"]')!;
43+
expect(el.getAttribute('data-height')).toBe('250');
44+
});
45+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script lang="ts">
2+
import { usePlot } from './usePlot.svelte.js';
3+
4+
const plot = usePlot();
5+
</script>
6+
7+
<div
8+
data-testid="plot-state"
9+
data-width={plot.width}
10+
data-height={plot.height}
11+
data-plot-width={plot.plotWidth}
12+
data-plot-height={plot.plotHeight}>
13+
</div>

0 commit comments

Comments
 (0)