@weasel-js/labkit 1.7.1 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chrome/index.js +4 -4
- package/dist/{chunk-GAHELLJT.js → chunk-RODIY7HI.js} +7 -7
- package/dist/{chunk-GAHELLJT.js.map → chunk-RODIY7HI.js.map} +1 -1
- package/dist/{chunk-4KSC7NXL.js → chunk-U6UQVGFP.js} +1984 -1892
- package/dist/chunk-U6UQVGFP.js.map +1 -0
- package/dist/chunk-WFALF2X2.js +302 -0
- package/dist/chunk-WFALF2X2.js.map +1 -0
- package/dist/{chunk-JFOJESJF.js → chunk-WWJDPNZZ.js} +22 -22
- package/dist/chunk-WWJDPNZZ.js.map +1 -0
- package/dist/{chunk-CEGFXTBD.js → chunk-XRX5S5NT.js} +4 -4
- package/dist/{chunk-CEGFXTBD.js.map → chunk-XRX5S5NT.js.map} +1 -1
- package/dist/{chunk-FB4MIZOO.js → chunk-XZDCEMEL.js} +6 -6
- package/dist/chunk-XZDCEMEL.js.map +1 -0
- package/dist/controls/index.d.ts +55 -3
- package/dist/controls/index.js +3 -2
- package/dist/fonts/oswald-tabular-digits.woff2 +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +135 -134
- package/dist/index.js.map +1 -1
- package/dist/layers/index.js +1 -1
- package/dist/passthrough/weasel-ui.js +1 -1
- package/dist/primitives/index.js +3 -3
- package/dist/styles.css +228 -11
- package/package.json +11 -11
- package/src/annotations/capture.browser.test.ts +9 -3
- package/src/canvas/Stage.test.tsx +34 -25
- package/src/chrome/labRegions.test.tsx +37 -26
- package/src/chrome/regions/PaletteRegion.test.tsx +2 -2
- package/src/controls/ControlMatrix.less +245 -0
- package/src/controls/ControlMatrix.stories.less +13 -0
- package/src/controls/ControlMatrix.stories.tsx +135 -0
- package/src/controls/ControlMatrix.test.tsx +206 -0
- package/src/controls/ControlMatrix.tsx +446 -0
- package/src/controls/ControlPanel.tsx +4 -2
- package/src/controls/index.ts +6 -0
- package/src/dragdrop/dragDrop.test.tsx +34 -33
- package/src/index.ts +6 -0
- package/src/lab/Lab.chrome.test.tsx +15 -10
- package/src/lab/Lab.nebula.test.tsx +5 -5
- package/src/lab/Lab.surface.test.tsx +30 -21
- package/src/lab/Lab.test.tsx +75 -74
- package/src/lab/Lab.undock.test.tsx +15 -10
- package/src/lab/LabHeader.test.tsx +15 -14
- package/src/lab/LabPalette.test.tsx +12 -7
- package/src/lab/LabShell.less +1 -2
- package/src/lab/LabZoom.test.tsx +55 -44
- package/src/lab/Workspace.surface.test.tsx +16 -13
- package/src/lab/Workspace.test.tsx +32 -27
- package/src/overview/OverviewMarks.test.tsx +9 -8
- package/src/overview/TrialOverview.test.tsx +18 -17
- package/src/primitives/FpsMeter.less +1 -2
- package/src/primitives/JobProgress.less +1 -2
- package/src/primitives/ScaleIndicator.less +1 -1
- package/src/primitives/StatusBar.less +1 -1
- package/src/specimen/Specimen.less +1 -0
- package/src/styles.less +3 -0
- package/src/trial/Trial.annotations.persist.test.tsx +20 -9
- package/src/trial/Trial.annotations.test.tsx +46 -41
- package/src/trial/Trial.canvas.test.tsx +21 -17
- package/src/trial/Trial.config.test.tsx +34 -27
- package/src/trial/Trial.loupe.test.tsx +27 -18
- package/src/trial/Trial.targets.test.tsx +9 -8
- package/src/trial/Trial.test.tsx +51 -46
- package/src/trial/Trial.trialId.test.tsx +7 -6
- package/dist/chunk-4KSC7NXL.js.map +0 -1
- package/dist/chunk-FB4MIZOO.js.map +0 -1
- package/dist/chunk-JFOJESJF.js.map +0 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// A sidebar-width frame for the ControlMatrix stories.
|
|
2
|
+
.lk-control-matrix-story {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: var(--wzl-space-5);
|
|
6
|
+
width: 360px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.lk-control-matrix-story__note {
|
|
10
|
+
margin: 0;
|
|
11
|
+
color: var(--wzl-fg-muted);
|
|
12
|
+
font-size: var(--wzl-font-size-sm);
|
|
13
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@weasel-js/forge';
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import { isAuto } from '../config/auto';
|
|
4
|
+
import { f } from '../config/builder';
|
|
5
|
+
import { valueAtPath, withValueAtPath } from '../config/path';
|
|
6
|
+
import { resolveConfigSchema } from '../config/resolve';
|
|
7
|
+
import { ControlMatrix, type ControlMatrixColumn } from './ControlMatrix';
|
|
8
|
+
import { ControlPanel } from './ControlPanel';
|
|
9
|
+
import './ControlMatrix.stories.less';
|
|
10
|
+
|
|
11
|
+
const meta: Meta<typeof ControlMatrix> = {
|
|
12
|
+
title: 'labkit/Controls/ControlMatrix',
|
|
13
|
+
component: ControlMatrix,
|
|
14
|
+
parameters: { layout: 'padded' },
|
|
15
|
+
};
|
|
16
|
+
export default meta;
|
|
17
|
+
|
|
18
|
+
type Story = StoryObj<typeof ControlMatrix>;
|
|
19
|
+
|
|
20
|
+
const look = () => ({
|
|
21
|
+
background: f.boolean(true).describe('Paint the box behind the primitive.'),
|
|
22
|
+
fill: f.color('#2f5d8a'),
|
|
23
|
+
border: f.number(1).range(0, 4).step(0.5).input().suffix('px'),
|
|
24
|
+
shading: f.enum('flat', [
|
|
25
|
+
{ value: 'flat', label: 'Flat' },
|
|
26
|
+
{ value: 'soft', label: 'Soft' },
|
|
27
|
+
{ value: 'bevel', label: 'Bevel' },
|
|
28
|
+
]),
|
|
29
|
+
glow: f.number(0).range(0, 1).step(0.05).describe('Halo strength around the primitive.'),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const primitives = ['window', 'page', 'slab', 'row', 'tile', 'arrow'] as const;
|
|
33
|
+
const LOOK_KEYS = Object.keys(look()) as (keyof ReturnType<typeof look>)[];
|
|
34
|
+
|
|
35
|
+
const config = f.schema({
|
|
36
|
+
looks: f.group({
|
|
37
|
+
defaults: f.group(look()),
|
|
38
|
+
window: f.group(look()),
|
|
39
|
+
page: f.group(look()),
|
|
40
|
+
slab: f.group(look()),
|
|
41
|
+
row: f.group(look()),
|
|
42
|
+
tile: f.group(look()),
|
|
43
|
+
// An arrow is a line: it has nothing to fill and no box behind it.
|
|
44
|
+
arrow: f.group({ border: look().border, shading: look().shading, glow: look().glow }),
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const columns: ControlMatrixColumn[] = [
|
|
49
|
+
{ key: 'looks.defaults', label: 'Def', title: 'Defaults' },
|
|
50
|
+
{ key: 'looks.window', label: 'Win', title: 'Window' },
|
|
51
|
+
{ key: 'looks.page', label: 'Page', title: 'Page' },
|
|
52
|
+
{ key: 'looks.slab', label: 'Slab', title: 'Slab' },
|
|
53
|
+
{ key: 'looks.row', label: 'Row', title: 'Row' },
|
|
54
|
+
{ key: 'looks.tile', label: 'Tile', title: 'Tile' },
|
|
55
|
+
{ key: 'looks.arrow', label: 'Arr', title: 'Arrow' },
|
|
56
|
+
];
|
|
57
|
+
const rows = LOOK_KEYS.map((key) => ({ key }));
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The store the consumer keeps: Defaults always, a primitive's setting only
|
|
61
|
+
* once it is pinned. A missing value inherits.
|
|
62
|
+
*/
|
|
63
|
+
function useLooks() {
|
|
64
|
+
const schema = useMemo(() => resolveConfigSchema(config, []), []);
|
|
65
|
+
const [stored, setStored] = useState<Record<string, unknown>>(() => ({
|
|
66
|
+
looks: {
|
|
67
|
+
defaults: config.defaults().looks.defaults,
|
|
68
|
+
window: { fill: '#6b3f8f', glow: 0.35 },
|
|
69
|
+
arrow: { border: 2 },
|
|
70
|
+
},
|
|
71
|
+
}));
|
|
72
|
+
const autoPaths = new Set<string>();
|
|
73
|
+
let shown: Record<string, unknown> = stored;
|
|
74
|
+
for (const p of primitives) {
|
|
75
|
+
for (const key of LOOK_KEYS) {
|
|
76
|
+
const path = `looks.${p}.${key}`;
|
|
77
|
+
if (valueAtPath(stored, path) !== undefined) continue;
|
|
78
|
+
autoPaths.add(path);
|
|
79
|
+
shown = withValueAtPath(shown, path, valueAtPath(stored, `looks.defaults.${key}`));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const setConfig = (path: string, value: unknown) =>
|
|
83
|
+
setStored((prev) => withValueAtPath(prev, path, isAuto(value) ? undefined : value));
|
|
84
|
+
return { schema, shown, autoPaths, setConfig };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Six primitives and their Defaults in a 360px sidebar. Click a cell to edit
|
|
88
|
+
* it, Option-click a pinned one to hand it back to Defaults. */
|
|
89
|
+
export const Looks: Story = {
|
|
90
|
+
render: function Render() {
|
|
91
|
+
const { schema, shown, autoPaths, setConfig } = useLooks();
|
|
92
|
+
const [focused, setFocused] = useState<string | null>(null);
|
|
93
|
+
return (
|
|
94
|
+
<div className="lk-control-matrix-story">
|
|
95
|
+
<ControlMatrix
|
|
96
|
+
title="Looks"
|
|
97
|
+
schema={schema}
|
|
98
|
+
columns={columns}
|
|
99
|
+
rows={rows}
|
|
100
|
+
config={shown}
|
|
101
|
+
auto={autoPaths}
|
|
102
|
+
setConfig={setConfig}
|
|
103
|
+
onColumnClick={(c) => setFocused(c.title ?? c.label)}
|
|
104
|
+
/>
|
|
105
|
+
{focused ? <p className="lk-control-matrix-story__note">Clicked {focused}</p> : null}
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/** The matrix under the panel it sits beside, to check the two read as one family. */
|
|
112
|
+
export const BesideControlPanel: Story = {
|
|
113
|
+
render: function Render() {
|
|
114
|
+
const { schema, shown, autoPaths, setConfig } = useLooks();
|
|
115
|
+
return (
|
|
116
|
+
<div className="lk-control-matrix-story">
|
|
117
|
+
<ControlPanel
|
|
118
|
+
title="Defaults"
|
|
119
|
+
schema={resolveConfigSchema(f.schema(look()), [])}
|
|
120
|
+
config={valueAtPath(shown, 'looks.defaults') as Record<string, unknown>}
|
|
121
|
+
setConfig={(path, value) => setConfig(`looks.defaults.${path}`, value)}
|
|
122
|
+
/>
|
|
123
|
+
<ControlMatrix
|
|
124
|
+
title="Per primitive"
|
|
125
|
+
schema={schema}
|
|
126
|
+
columns={columns}
|
|
127
|
+
rows={rows}
|
|
128
|
+
config={shown}
|
|
129
|
+
auto={autoPaths}
|
|
130
|
+
setConfig={setConfig}
|
|
131
|
+
/>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
},
|
|
135
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { fireEvent, render, screen, within } from '@testing-library/react';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { auto } from '../config/auto';
|
|
4
|
+
import { f } from '../config/builder';
|
|
5
|
+
import { resolveConfigSchema } from '../config/resolve';
|
|
6
|
+
import { ControlMatrix, type ControlMatrixColumn } from './ControlMatrix';
|
|
7
|
+
|
|
8
|
+
const look = () => ({
|
|
9
|
+
background: f.boolean(true).label('Background'),
|
|
10
|
+
fill: f.color('#336699').label('Fill'),
|
|
11
|
+
glow: f.number(0.25).range(0, 1).step(0.05).label('Glow').describe('Halo around the shape.'),
|
|
12
|
+
shading: f.enum('flat', [
|
|
13
|
+
{ value: 'flat', label: 'Flat' },
|
|
14
|
+
{ value: 'soft', label: 'Soft' },
|
|
15
|
+
]),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const schema = resolveConfigSchema(
|
|
19
|
+
f.schema({
|
|
20
|
+
looks: f.group({
|
|
21
|
+
defaults: f.group(look()),
|
|
22
|
+
window: f.group(look()),
|
|
23
|
+
// An arrow has no fill.
|
|
24
|
+
arrow: f.group({ background: look().background, glow: look().glow, shading: look().shading }),
|
|
25
|
+
}),
|
|
26
|
+
}),
|
|
27
|
+
[],
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const columns: ControlMatrixColumn[] = [
|
|
31
|
+
{ key: 'looks.defaults', label: 'Def', title: 'Defaults' },
|
|
32
|
+
{ key: 'looks.window', label: 'Win', title: 'Window' },
|
|
33
|
+
{ key: 'looks.arrow', label: 'Arr', title: 'Arrow' },
|
|
34
|
+
];
|
|
35
|
+
const rows = [{ key: 'background' }, { key: 'fill' }, { key: 'glow' }, { key: 'shading' }];
|
|
36
|
+
|
|
37
|
+
const value = { background: true, fill: '#336699', glow: 0.25, shading: 'flat' };
|
|
38
|
+
const config = {
|
|
39
|
+
looks: {
|
|
40
|
+
defaults: value,
|
|
41
|
+
window: { ...value, glow: 0.5 },
|
|
42
|
+
arrow: { background: true, glow: 0.25, shading: 'soft' },
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function setup(over: Partial<Parameters<typeof ControlMatrix>[0]> = {}) {
|
|
47
|
+
const setConfig = vi.fn();
|
|
48
|
+
const utils = render(
|
|
49
|
+
<ControlMatrix
|
|
50
|
+
schema={schema}
|
|
51
|
+
columns={columns}
|
|
52
|
+
rows={rows}
|
|
53
|
+
config={config}
|
|
54
|
+
auto={new Set(['looks.window.background', 'looks.window.fill', 'looks.arrow.glow'])}
|
|
55
|
+
setConfig={setConfig}
|
|
56
|
+
{...over}
|
|
57
|
+
/>,
|
|
58
|
+
);
|
|
59
|
+
return { setConfig, ...utils };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const cell = (name: string) => screen.getByLabelText(name, { exact: false });
|
|
63
|
+
|
|
64
|
+
describe('<ControlMatrix>', () => {
|
|
65
|
+
it('draws a table of settings by scopes, labels from the schema', () => {
|
|
66
|
+
setup();
|
|
67
|
+
const table = screen.getByRole('table');
|
|
68
|
+
expect(
|
|
69
|
+
within(table)
|
|
70
|
+
.getAllByRole('columnheader')
|
|
71
|
+
.map((h) => h.textContent),
|
|
72
|
+
).toEqual(['Def', 'Win', 'Arr']);
|
|
73
|
+
expect(
|
|
74
|
+
within(table)
|
|
75
|
+
.getAllByRole('rowheader')
|
|
76
|
+
.map((h) => h.textContent),
|
|
77
|
+
).toEqual(['Background', 'Fill', 'Glowⓘ', 'Shading']);
|
|
78
|
+
expect(screen.getByRole('button', { name: 'About Glow' })).toBeTruthy();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('shows numbers at their step precision and enums by label', () => {
|
|
82
|
+
setup();
|
|
83
|
+
expect(cell('Defaults Glow').textContent).toBe('0.25');
|
|
84
|
+
expect(cell('Window Glow').textContent).toBe('0.50');
|
|
85
|
+
expect(cell('Arrow Shading').textContent).toBe('Soft');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('draws a dash for a scope with no such setting', () => {
|
|
89
|
+
setup();
|
|
90
|
+
const none = screen.getByLabelText('Arrow has no Fill');
|
|
91
|
+
expect(none.textContent).toBe('—');
|
|
92
|
+
expect(none.closest('td')?.className).toMatch(/is-missing/);
|
|
93
|
+
expect(none.closest('td')?.querySelector('button, input')).toBeNull();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('ghosts an inherited cell and names where it inherits from', () => {
|
|
97
|
+
setup();
|
|
98
|
+
const inherited = screen.getByRole('switch', { name: 'Window Background, from Defaults' });
|
|
99
|
+
expect(inherited.closest('td')?.className).toMatch(/is-inherited/);
|
|
100
|
+
const pinned = screen.getByRole('switch', { name: 'Defaults Background' });
|
|
101
|
+
expect(pinned.closest('td')?.className).not.toMatch(/is-inherited/);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('takes the inherit hint from a prop', () => {
|
|
105
|
+
setup({ inheritHint: (c) => `${c.title} follows the default` });
|
|
106
|
+
expect(
|
|
107
|
+
screen.getByRole('switch', { name: 'Window Background, Window follows the default' }),
|
|
108
|
+
).toBeTruthy();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('flips a boolean in place, which pins it', () => {
|
|
112
|
+
const { setConfig } = setup();
|
|
113
|
+
fireEvent.click(screen.getByRole('switch', { name: /^Window Background/ }));
|
|
114
|
+
expect(setConfig).toHaveBeenCalledWith('looks.window.background', false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('opens a color picker in place, which pins it', () => {
|
|
118
|
+
const { setConfig } = setup();
|
|
119
|
+
const swatch = cell('Window Fill') as HTMLInputElement;
|
|
120
|
+
expect(swatch.type).toBe('color');
|
|
121
|
+
fireEvent.change(swatch, { target: { value: '#ff0000' } });
|
|
122
|
+
expect(setConfig).toHaveBeenCalledWith('looks.window.fill', '#ff0000');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('draws an unset color as unset, and clears a set one from its popover', () => {
|
|
126
|
+
const edge = resolveConfigSchema(
|
|
127
|
+
f.schema({
|
|
128
|
+
a: f.group({ edge: f.color('').label('Edge') }),
|
|
129
|
+
b: f.group({ edge: f.color('').label('Edge') }),
|
|
130
|
+
}),
|
|
131
|
+
[],
|
|
132
|
+
);
|
|
133
|
+
const setConfig = vi.fn();
|
|
134
|
+
render(
|
|
135
|
+
<ControlMatrix
|
|
136
|
+
schema={edge}
|
|
137
|
+
columns={[
|
|
138
|
+
{ key: 'a', label: 'A', title: 'Alpha' },
|
|
139
|
+
{ key: 'b', label: 'B', title: 'Beta' },
|
|
140
|
+
]}
|
|
141
|
+
rows={[{ key: 'edge' }]}
|
|
142
|
+
config={{ a: { edge: null }, b: { edge: '#ff0000' } }}
|
|
143
|
+
auto={new Set()}
|
|
144
|
+
setConfig={setConfig}
|
|
145
|
+
/>,
|
|
146
|
+
);
|
|
147
|
+
expect(screen.getByRole('button', { name: 'Alpha Edge, unset' })).toBeTruthy();
|
|
148
|
+
fireEvent.click(screen.getByRole('button', { name: 'Beta Edge' }));
|
|
149
|
+
fireEvent.click(screen.getByRole('button', { name: 'Clear' }));
|
|
150
|
+
expect(setConfig).toHaveBeenCalledWith('b.edge', null);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('edits any other cell in a popover holding the panel control, which pins it', () => {
|
|
154
|
+
const { setConfig } = setup();
|
|
155
|
+
fireEvent.click(cell('Window Glow'));
|
|
156
|
+
const dialog = screen.getByRole('dialog', { name: 'Window Glow' });
|
|
157
|
+
fireEvent.change(within(dialog).getByRole('slider'), { target: { value: '0.75' } });
|
|
158
|
+
expect(setConfig).toHaveBeenCalledWith('looks.window.glow', 0.75);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('unpins from the popover with Inherit, disabled while already inherited', () => {
|
|
162
|
+
const { setConfig, unmount } = setup();
|
|
163
|
+
fireEvent.click(cell('Window Glow'));
|
|
164
|
+
const inherit = screen.getByRole('button', { name: 'Inherit' });
|
|
165
|
+
expect(inherit).not.toBeDisabled();
|
|
166
|
+
fireEvent.click(inherit);
|
|
167
|
+
expect(setConfig).toHaveBeenCalledWith('looks.window.glow', auto);
|
|
168
|
+
unmount();
|
|
169
|
+
|
|
170
|
+
setup();
|
|
171
|
+
fireEvent.click(cell('Arrow Glow'));
|
|
172
|
+
expect(screen.getByRole('button', { name: 'Inherit' })).toBeDisabled();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('has no Inherit for the fallback column', () => {
|
|
176
|
+
setup();
|
|
177
|
+
fireEvent.click(cell('Defaults Glow'));
|
|
178
|
+
expect(screen.getByRole('dialog', { name: 'Defaults Glow' })).toBeTruthy();
|
|
179
|
+
expect(screen.queryByRole('button', { name: 'Inherit' })).toBeNull();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('unpins a pinned cell on Alt-click, without flipping or opening it', () => {
|
|
183
|
+
const { setConfig } = setup();
|
|
184
|
+
fireEvent.click(cell('Window Glow'), { altKey: true });
|
|
185
|
+
expect(setConfig).toHaveBeenCalledWith('looks.window.glow', auto);
|
|
186
|
+
expect(screen.queryByRole('dialog')).toBeNull();
|
|
187
|
+
|
|
188
|
+
setConfig.mockClear();
|
|
189
|
+
fireEvent.click(screen.getByRole('switch', { name: 'Defaults Background' }), { altKey: true });
|
|
190
|
+
fireEvent.click(screen.getByRole('switch', { name: /^Window Background/ }), { altKey: true });
|
|
191
|
+
// The fallback cannot inherit, and an inherited cell is already unpinned.
|
|
192
|
+
expect(setConfig).not.toHaveBeenCalled();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('calls onColumnClick from a header button', () => {
|
|
196
|
+
const onColumnClick = vi.fn();
|
|
197
|
+
setup({ onColumnClick });
|
|
198
|
+
fireEvent.click(screen.getByRole('button', { name: 'Window' }));
|
|
199
|
+
expect(onColumnClick).toHaveBeenCalledWith(columns[1]);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('draws plain headers without onColumnClick', () => {
|
|
203
|
+
setup();
|
|
204
|
+
expect(screen.queryByRole('button', { name: 'Window' })).toBeNull();
|
|
205
|
+
});
|
|
206
|
+
});
|