@weasel-js/labkit 1.6.0 → 1.7.0
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/_dts/{CanvasStackContext-DQgcH1ny.d.ts → CanvasStackContext-D2jnIwfk.d.ts} +1 -1
- package/dist/_dts/FloatingPanel-7j0IcXwz.d.ts +30 -0
- package/dist/_dts/{LayerList.d-2c9yup5Y.d.ts → LayerList.d-DiuEArKq.d.ts} +4 -4
- package/dist/_dts/PrefsRow.d-TOZ7BgD9.d.ts +79 -0
- package/dist/_dts/PropertyPanel.d-D-1paCbE.d.ts +141 -0
- package/dist/_dts/{Select.d-BchGMC2Y.d.ts → Select.d-Bensr_MT.d.ts} +15 -6
- package/dist/_dts/Stage-CCWfQQWM.d.ts +202 -0
- package/dist/_dts/index-B2Yj5aA0.d.ts +274 -0
- package/dist/_dts/{index-B0--8PEM.d.ts → index-CsYqUHD3.d.ts} +12 -13
- package/dist/_dts/{types-B7y8hX1D.d.ts → types-CRPSGFAp.d.ts} +33 -5
- package/dist/_dts/{frac-DTRq7Xpb.d.ts → types-CSm9jgol.d.ts} +700 -650
- package/dist/_dts/{useTrialState-CP4jfuKl.d.ts → useTrialState-CTZPgeZ7.d.ts} +2 -3
- package/dist/canvas/index.d.ts +40 -8
- package/dist/canvas/index.js +5 -3
- package/dist/chrome/index.d.ts +5 -6
- package/dist/chrome/index.js +9 -7
- package/dist/chunk-24W5VEJW.js +18 -0
- package/dist/chunk-24W5VEJW.js.map +1 -0
- package/dist/chunk-275JRRVD.js +728 -0
- package/dist/chunk-275JRRVD.js.map +1 -0
- package/dist/chunk-3RY6WL5T.js +38 -0
- package/dist/chunk-3RY6WL5T.js.map +1 -0
- package/dist/chunk-4LJSC7MB.js +143 -0
- package/dist/chunk-4LJSC7MB.js.map +1 -0
- package/dist/{chunk-3FI7Y4R4.js → chunk-7BO7HNGF.js} +5 -5
- package/dist/{chunk-3FI7Y4R4.js.map → chunk-7BO7HNGF.js.map} +1 -1
- package/dist/chunk-AWQG7FCC.js +19 -0
- package/dist/chunk-AWQG7FCC.js.map +1 -0
- package/dist/chunk-CZZPXUOU.js +3 -0
- package/dist/{chunk-SBXNQW4G.js.map → chunk-CZZPXUOU.js.map} +1 -1
- package/dist/{chunk-ZORVNOKX.js → chunk-FMK7UQ6V.js} +24 -145
- package/dist/chunk-FMK7UQ6V.js.map +1 -0
- package/dist/{chunk-BVL7Y574.js → chunk-FZ7YFQN7.js} +26 -10
- package/dist/chunk-FZ7YFQN7.js.map +1 -0
- package/dist/{chunk-USO6TERP.js → chunk-GOZLNM4L.js} +11 -9
- package/dist/chunk-GOZLNM4L.js.map +1 -0
- package/dist/{chunk-H5VIV7MQ.js → chunk-I5DUQ5HM.js} +88 -82
- package/dist/chunk-I5DUQ5HM.js.map +1 -0
- package/dist/chunk-J7OWWTCW.js +143 -0
- package/dist/chunk-J7OWWTCW.js.map +1 -0
- package/dist/{chunk-H6ZAOWNE.js → chunk-JVBYV6TL.js} +3 -3
- package/dist/{chunk-H6ZAOWNE.js.map → chunk-JVBYV6TL.js.map} +1 -1
- package/dist/{chunk-VQ47KWD5.js → chunk-KMJ7RWD3.js} +18 -7
- package/dist/chunk-KMJ7RWD3.js.map +1 -0
- package/dist/{chunk-ERFMC6WU.js → chunk-M7HP3FTK.js} +20 -11
- package/dist/chunk-M7HP3FTK.js.map +1 -0
- package/dist/chunk-MIM5R4ZT.js +14972 -0
- package/dist/chunk-MIM5R4ZT.js.map +1 -0
- package/dist/{chunk-HFRE4QQV.js → chunk-T5IBSPAM.js} +96 -32
- package/dist/chunk-T5IBSPAM.js.map +1 -0
- package/dist/chunk-UNAZSLWE.js +294 -0
- package/dist/chunk-UNAZSLWE.js.map +1 -0
- package/dist/{chunk-RS3HRQWU.js → chunk-WPLAANH4.js} +4 -3
- package/dist/chunk-WPLAANH4.js.map +1 -0
- package/dist/{chunk-AWH3SRPW.js → chunk-Y23G2VZL.js} +5 -37
- package/dist/chunk-Y23G2VZL.js.map +1 -0
- package/dist/{chunk-MOLIUNVH.js → chunk-YT75VYRD.js} +4 -4
- package/dist/{chunk-MOLIUNVH.js.map → chunk-YT75VYRD.js.map} +1 -1
- package/dist/chunk-ZIFPHIWZ.js +302 -0
- package/dist/chunk-ZIFPHIWZ.js.map +1 -0
- package/dist/config/index.d.ts +48 -40
- package/dist/config/index.js +4 -5
- package/dist/controls/index.d.ts +91 -7
- package/dist/controls/index.js +5 -4
- package/dist/dragdrop/index.d.ts +9 -7
- package/dist/dragdrop/index.js +3 -2
- package/dist/index.d.ts +294 -82
- package/dist/index.js +425 -402
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +4 -5
- package/dist/layers/index.js +2 -1
- package/dist/loupe/index.d.ts +20 -17
- package/dist/loupe/index.js +5 -3
- package/dist/overview/index.d.ts +56 -0
- package/dist/overview/index.js +289 -0
- package/dist/overview/index.js.map +1 -0
- package/dist/passthrough/weasel-ui.d.ts +239 -69
- package/dist/passthrough/weasel-ui.js +3 -2
- package/dist/primitives/index.d.ts +5 -204
- package/dist/primitives/index.js +6 -4
- package/dist/state/index.d.ts +8 -8
- package/dist/state/index.js +4 -5
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +152 -82
- package/dist/undo/index.d.ts +4 -4
- package/package.json +17 -12
- package/src/annotations/AnnotationOverlay.tsx +2 -2
- package/src/annotations/Annotations.less +0 -10
- package/src/annotations/MarkList.tsx +4 -8
- package/src/annotations/index.ts +1 -0
- package/src/annotations/paint.test.ts +3 -14
- package/src/annotations/paint.ts +3 -19
- package/src/annotations/store.test.ts +26 -0
- package/src/annotations/store.ts +25 -6
- package/src/annotations/svgNodes.test.ts +6 -9
- package/src/annotations/svgNodes.ts +3 -3
- package/src/annotations/toolMap.test.ts +37 -2
- package/src/annotations/toolMap.ts +13 -0
- package/src/annotations/types.ts +27 -0
- package/src/canvas/AGENTS.md +9 -10
- package/src/canvas/CameraInput.test.tsx +136 -0
- package/src/canvas/CameraInput.tsx +322 -0
- package/src/canvas/CanvasStack.tsx +47 -58
- package/src/canvas/LinkedCursor.less +16 -0
- package/src/canvas/LinkedCursor.tsx +54 -0
- package/src/canvas/Stage.test.tsx +33 -4
- package/src/canvas/Stage.tsx +47 -37
- package/src/canvas/cameraRegistry.test.ts +55 -0
- package/src/canvas/cameraRegistry.ts +86 -0
- package/src/canvas/cameraView.test.ts +60 -0
- package/src/canvas/cameraView.ts +61 -0
- package/src/canvas/cameraZoom.test.ts +46 -0
- package/src/canvas/cameraZoom.ts +24 -0
- package/src/canvas/index.ts +23 -2
- package/src/canvas/sharedScope.test.tsx +94 -0
- package/src/chrome/ChromeRegions.stories.tsx +1 -0
- package/src/chrome/regions/PaletteRegion.less +0 -7
- package/src/chrome/regions/SidebarRegion.less +12 -2
- package/src/chrome/regions/SidebarRegion.test.tsx +9 -2
- package/src/config/builder.test.ts +2 -1
- package/src/config/builder.ts +15 -8
- package/src/config/entry.test.ts +3 -1
- package/src/config/fromConfigField.test.ts +14 -0
- package/src/config/fromConfigField.ts +19 -4
- package/src/config/index.ts +4 -1
- package/src/config/path.ts +2 -5
- package/src/config/resolve.ts +15 -1
- package/src/config/sectionTree.test.ts +12 -6
- package/src/config/sectionTree.ts +15 -5
- package/src/config/types.ts +34 -9
- package/src/controls/ControlPanel.less +1 -1
- package/src/controls/ControlPanel.stories.tsx +3 -2
- package/src/controls/ControlPanel.test.tsx +76 -13
- package/src/controls/ControlPanel.tsx +80 -11
- package/src/controls/inDialog.test.tsx +18 -0
- package/src/controls/inDialog.tsx +2 -10
- package/src/index.test.ts +1 -1
- package/src/index.ts +23 -5
- package/src/instrument/SineWave.smoke.test.tsx +2 -0
- package/src/instrument/types.ts +10 -1
- package/src/lab/Lab.nebula.test.tsx +11 -2
- package/src/lab/Lab.test.tsx +3 -1
- package/src/lab/Lab.tsx +87 -76
- package/src/lab/{LabFit.stories.less → LabFit.browser.test.less} +1 -1
- package/src/lab/{LabFit.stories.tsx → LabFit.browser.test.tsx} +41 -63
- package/src/lab/LabFullChrome.stories.tsx +1 -0
- package/src/lab/LabHeader.test.tsx +24 -14
- package/src/lab/LabHeader.tsx +11 -12
- package/src/lab/LabShell.less +8 -17
- package/src/lab/LabSwitcher.less +6 -6
- package/src/lab/LabZoom.test.tsx +223 -0
- package/src/lab/LabZoom.tsx +170 -0
- package/src/lab/index.ts +1 -0
- package/src/loupe/AGENTS.md +7 -6
- package/src/loupe/LoupeGestures.tsx +15 -10
- package/src/loupe/TrialLoupe.tsx +5 -5
- package/src/loupe/index.ts +1 -0
- package/src/loupe/loupeActions.ts +6 -2
- package/src/overview/AGENTS.md +38 -0
- package/src/overview/OverviewMarks.test.tsx +100 -0
- package/src/overview/OverviewMarks.tsx +93 -0
- package/src/overview/TrialOverview.less +53 -0
- package/src/overview/TrialOverview.stories.tsx +50 -0
- package/src/overview/TrialOverview.test.tsx +116 -0
- package/src/overview/TrialOverview.tsx +376 -0
- package/src/overview/index.ts +2 -0
- package/src/passthrough/weasel-ui.test.ts +2 -0
- package/src/passthrough/weasel-ui.ts +23 -4
- package/src/primitives/FloatingPanel.test.tsx +11 -0
- package/src/primitives/FloatingPanel.tsx +2 -5
- package/src/primitives/JobProgress.less +1 -1
- package/src/primitives/Legend.stories.tsx +1 -0
- package/src/primitives/Readout.browser.test.tsx +137 -0
- package/src/primitives/Readout.less +12 -0
- package/src/primitives/Readout.stories.tsx +58 -0
- package/src/primitives/Readout.test.tsx +93 -0
- package/src/primitives/Readout.tsx +56 -0
- package/src/primitives/index.ts +2 -0
- package/src/specimen/Specimen.stories.tsx +1 -0
- package/src/specimen/Specimen.tsx +21 -4
- package/src/state/Persistence.browser.test.tsx +47 -0
- package/src/state/labRecords.test.ts +4 -1
- package/src/state/store.test.ts +9 -5
- package/src/state/usePersistedState.test.tsx +1 -1
- package/src/state/useTrialState.test.tsx +1 -1
- package/src/styles.less +3 -0
- package/src/theme/Interstellar.stories.tsx +4 -6
- package/src/theme/base.less +21 -47
- package/src/theme/interstellar.test.ts +1 -1
- package/src/theme/interstellar.theme.json +10 -3
- package/src/tools/labTool.ts +6 -2
- package/src/trial/Trial.annotations.test.tsx +16 -0
- package/src/trial/Trial.less +0 -13
- package/src/trial/Trial.loupe.test.tsx +73 -1
- package/src/trial/Trial.stories.tsx +2 -0
- package/src/trial/Trial.tsx +23 -1
- package/src/trial/TrialChrome.tsx +21 -3
- package/src/trial/trialOps.test.ts +4 -4
- package/dist/_dts/PrefsRow.d-DFpPIeIC.d.ts +0 -39
- package/dist/_dts/inDialog-D5ZKGjiD.d.ts +0 -23
- package/dist/_dts/index-DINmCuLA.d.ts +0 -453
- package/dist/_dts/number.d-OeQYQoUO.d.ts +0 -28
- package/dist/_dts/usePanZoom-Bq73pwFe.d.ts +0 -117
- package/dist/chunk-2AYGEN57.js +0 -32
- package/dist/chunk-2AYGEN57.js.map +0 -1
- package/dist/chunk-AWH3SRPW.js.map +0 -1
- package/dist/chunk-BVL7Y574.js.map +0 -1
- package/dist/chunk-ERFMC6WU.js.map +0 -1
- package/dist/chunk-H5VIV7MQ.js.map +0 -1
- package/dist/chunk-HFRE4QQV.js.map +0 -1
- package/dist/chunk-RS3HRQWU.js.map +0 -1
- package/dist/chunk-SBXNQW4G.js +0 -3
- package/dist/chunk-TT5C5S6R.js +0 -368
- package/dist/chunk-TT5C5S6R.js.map +0 -1
- package/dist/chunk-USO6TERP.js.map +0 -1
- package/dist/chunk-VQ47KWD5.js.map +0 -1
- package/dist/chunk-WUJXJC5O.js +0 -14568
- package/dist/chunk-WUJXJC5O.js.map +0 -1
- package/dist/chunk-ZORVNOKX.js.map +0 -1
- package/src/canvas/usePanZoom.test.ts +0 -244
- package/src/canvas/usePanZoom.ts +0 -147
- package/src/state/Persistence.stories.tsx +0 -75
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { act, fireEvent, render, screen, within } from '@testing-library/react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import { TOOL_PREF_KINDS, type ToolPrefKind } from '@weasel-js/core';
|
|
4
|
+
import { compact } from '@weasel-js/quantity';
|
|
4
5
|
import type { PrefLeaf } from '@weasel-js/ui';
|
|
5
6
|
import { useState } from 'react';
|
|
6
7
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
@@ -88,7 +89,7 @@ describe('<ControlPanel> number', () => {
|
|
|
88
89
|
];
|
|
89
90
|
render(<ControlPanel fields={fields} config={{ n: 42 }} setConfig={vi.fn()} />);
|
|
90
91
|
const input = screen.getByLabelText('N') as HTMLInputElement;
|
|
91
|
-
expect(input
|
|
92
|
+
expect(input).toHaveAttribute('role', 'spinbutton');
|
|
92
93
|
expect(input.value).toBe('42');
|
|
93
94
|
});
|
|
94
95
|
|
|
@@ -143,7 +144,7 @@ describe('<ControlPanel> color', () => {
|
|
|
143
144
|
const input = screen.getByLabelText('Color') as HTMLInputElement;
|
|
144
145
|
expect(input.type).toBe('color');
|
|
145
146
|
expect(input.value).toBe('#ff0000');
|
|
146
|
-
fireEvent.
|
|
147
|
+
fireEvent.input(input, { target: { value: '#00ff00' } });
|
|
147
148
|
expect(setConfig).toHaveBeenCalledWith('c', '#00ff00');
|
|
148
149
|
});
|
|
149
150
|
});
|
|
@@ -206,7 +207,7 @@ describe('<ControlPanel> schema', () => {
|
|
|
206
207
|
[],
|
|
207
208
|
);
|
|
208
209
|
render(<ControlPanel schema={schema} config={{ a: 5, b: 5 }} setConfig={vi.fn()} />);
|
|
209
|
-
expect(screen.getByLabelText('B')).toHaveAttribute('
|
|
210
|
+
expect(screen.getByLabelText('B')).toHaveAttribute('role', 'spinbutton');
|
|
210
211
|
});
|
|
211
212
|
|
|
212
213
|
it('falls back to the leaf default when config holds no value', () => {
|
|
@@ -372,6 +373,7 @@ describe('<ControlPanel> built-in kind coverage', () => {
|
|
|
372
373
|
sections: [],
|
|
373
374
|
showIf: new Map(),
|
|
374
375
|
renderers: {},
|
|
376
|
+
dialogs: {},
|
|
375
377
|
});
|
|
376
378
|
|
|
377
379
|
it('declines paint and object with a named row rather than a blank one', () => {
|
|
@@ -465,7 +467,7 @@ describe('<ControlPanel> suffix', () => {
|
|
|
465
467
|
|
|
466
468
|
it('suffixes a typed number with the suffix the leaf declares', () => {
|
|
467
469
|
const { container } = panel(f.number(20).input().suffix('px'));
|
|
468
|
-
expect(container.querySelector('input[
|
|
470
|
+
expect(container.querySelector('input[role="spinbutton"]')).not.toBeNull();
|
|
469
471
|
expect(container.textContent).toContain('px');
|
|
470
472
|
});
|
|
471
473
|
});
|
|
@@ -475,7 +477,7 @@ describe('<ControlPanel> format', () => {
|
|
|
475
477
|
render(
|
|
476
478
|
<ControlPanel
|
|
477
479
|
schema={resolveConfigSchema(
|
|
478
|
-
f.schema({ glyphs: f.number(0).range(0, 2_000_000).
|
|
480
|
+
f.schema({ glyphs: f.number(0).range(0, 2_000_000).display(compact()) }),
|
|
479
481
|
[],
|
|
480
482
|
)}
|
|
481
483
|
config={{ glyphs: 2_000_000 }}
|
|
@@ -897,6 +899,28 @@ describe('<ControlPanel> auto', () => {
|
|
|
897
899
|
});
|
|
898
900
|
});
|
|
899
901
|
|
|
902
|
+
describe('<ControlPanel> option icons', () => {
|
|
903
|
+
it("draws an option's icon in its segment and keeps the label as its name", () => {
|
|
904
|
+
const status = f
|
|
905
|
+
.enum('ok', [
|
|
906
|
+
{ value: 'ok', label: 'ok', icon: 'statusSuccess' },
|
|
907
|
+
{ value: 'bad', label: 'bad' },
|
|
908
|
+
])
|
|
909
|
+
.radio();
|
|
910
|
+
render(
|
|
911
|
+
<ControlPanel
|
|
912
|
+
schema={resolveConfigSchema(f.schema({ status }), [])}
|
|
913
|
+
config={{ status: 'ok' }}
|
|
914
|
+
setConfig={() => {}}
|
|
915
|
+
/>,
|
|
916
|
+
);
|
|
917
|
+
const ok = screen.getByRole('radio', { name: 'ok' });
|
|
918
|
+
expect(ok.querySelector('svg')).not.toBeNull();
|
|
919
|
+
expect(ok.textContent).toBe('');
|
|
920
|
+
expect(screen.getByRole('radio', { name: 'bad' }).textContent).toBe('bad');
|
|
921
|
+
});
|
|
922
|
+
});
|
|
923
|
+
|
|
900
924
|
describe('<ControlPanel> pair', () => {
|
|
901
925
|
const paired = () =>
|
|
902
926
|
resolveConfigSchema(
|
|
@@ -912,8 +936,8 @@ describe('<ControlPanel> pair', () => {
|
|
|
912
936
|
it('draws two leaves sharing a pair id as one row named by the pair', () => {
|
|
913
937
|
render(<ControlPanel schema={paired()} config={config} setConfig={vi.fn()} />);
|
|
914
938
|
const row = screen.getByText('Offset').closest('div') as HTMLElement;
|
|
915
|
-
expect(within(row).getByLabelText('X')).toHaveValue(10);
|
|
916
|
-
expect(within(row).getByLabelText('Y')).toHaveValue(20);
|
|
939
|
+
expect(within(row).getByLabelText('X')).toHaveValue('10');
|
|
940
|
+
expect(within(row).getByLabelText('Y')).toHaveValue('20');
|
|
917
941
|
});
|
|
918
942
|
|
|
919
943
|
it('captions each cell of a pair with its own name, so a paired switch is not left unnamed', () => {
|
|
@@ -951,7 +975,7 @@ describe('<ControlPanel> pair', () => {
|
|
|
951
975
|
render(<ControlPanel schema={paired()} config={config} setConfig={vi.fn()} />);
|
|
952
976
|
const row = screen.getByText('Offset').closest('div') as HTMLElement;
|
|
953
977
|
expect(within(row).queryByLabelText('Scale')).toBeNull();
|
|
954
|
-
expect(screen.getByLabelText('Scale')).toHaveValue(1);
|
|
978
|
+
expect(screen.getByLabelText('Scale')).toHaveValue('1');
|
|
955
979
|
});
|
|
956
980
|
});
|
|
957
981
|
|
|
@@ -1044,18 +1068,18 @@ describe('<ControlPanel> color alpha', () => {
|
|
|
1044
1068
|
it('splits the stored hex into a swatch and an alpha track', () => {
|
|
1045
1069
|
panel();
|
|
1046
1070
|
expect((screen.getByLabelText('Tint') as HTMLInputElement).value).toBe('#336699');
|
|
1047
|
-
expect((screen.getByLabelText('Tint opacity') as HTMLInputElement).value).toBe('
|
|
1071
|
+
expect((screen.getByLabelText('Tint opacity') as HTMLInputElement).value).toBe('80');
|
|
1048
1072
|
});
|
|
1049
1073
|
|
|
1050
1074
|
it('keeps the stored alpha when the swatch moves', () => {
|
|
1051
1075
|
const setConfig = panel();
|
|
1052
|
-
fireEvent.
|
|
1076
|
+
fireEvent.input(screen.getByLabelText('Tint'), { target: { value: '#00ff00' } });
|
|
1053
1077
|
expect(setConfig).toHaveBeenCalledWith('tint', '#00ff00cc');
|
|
1054
1078
|
});
|
|
1055
1079
|
|
|
1056
1080
|
it('keeps the stored color when the alpha track moves', () => {
|
|
1057
1081
|
const setConfig = panel();
|
|
1058
|
-
fireEvent.
|
|
1082
|
+
fireEvent.input(screen.getByLabelText('Tint opacity'), { target: { value: '50' } });
|
|
1059
1083
|
expect(setConfig).toHaveBeenCalledWith('tint', '#33669980');
|
|
1060
1084
|
});
|
|
1061
1085
|
|
|
@@ -1075,14 +1099,23 @@ describe('<ControlPanel> title, stance and tone', () => {
|
|
|
1075
1099
|
const fields: ConfigField[] = [{ key: 'on', label: 'On', type: 'checkbox', default: true }];
|
|
1076
1100
|
|
|
1077
1101
|
it('renders the bare list when given none of them', () => {
|
|
1078
|
-
const { container } = render(
|
|
1102
|
+
const { container } = render(
|
|
1103
|
+
<ControlPanel fields={fields} config={{ on: true }} setConfig={vi.fn()} />,
|
|
1104
|
+
);
|
|
1079
1105
|
expect(container.firstElementChild?.classList.contains('lk-control-panel')).toBe(true);
|
|
1080
1106
|
expect(container.querySelector('[data-stance], h2')).toBeNull();
|
|
1081
1107
|
});
|
|
1082
1108
|
|
|
1083
1109
|
it('wraps its rows in a panel carrying them when asked', () => {
|
|
1084
1110
|
const { container } = render(
|
|
1085
|
-
<ControlPanel
|
|
1111
|
+
<ControlPanel
|
|
1112
|
+
title="View"
|
|
1113
|
+
stance="scope"
|
|
1114
|
+
tone={1}
|
|
1115
|
+
fields={fields}
|
|
1116
|
+
config={{ on: true }}
|
|
1117
|
+
setConfig={vi.fn()}
|
|
1118
|
+
/>,
|
|
1086
1119
|
);
|
|
1087
1120
|
const panel = container.firstElementChild as HTMLElement;
|
|
1088
1121
|
expect(panel.dataset.stance).toBe('scope');
|
|
@@ -1091,3 +1124,33 @@ describe('<ControlPanel> title, stance and tone', () => {
|
|
|
1091
1124
|
expect(panel.querySelector('.lk-control-panel')).not.toBeNull();
|
|
1092
1125
|
});
|
|
1093
1126
|
});
|
|
1127
|
+
|
|
1128
|
+
describe('<ControlPanel> resettable group', () => {
|
|
1129
|
+
const schema = resolveConfigSchema(
|
|
1130
|
+
f.schema({
|
|
1131
|
+
name: f.string('a'),
|
|
1132
|
+
modes: f.group({ one: f.number(0), two: f.number(0), inner: f.group({ three: f.number(1) }) }).resettable(),
|
|
1133
|
+
plain: f.group({ four: f.number(0) }),
|
|
1134
|
+
}),
|
|
1135
|
+
);
|
|
1136
|
+
|
|
1137
|
+
it('puts a reset button on the group heading, disabled while nothing has changed', () => {
|
|
1138
|
+
render(
|
|
1139
|
+
<ControlPanel schema={schema} config={{ name: 'a', modes: { one: 0, two: 0, inner: { three: 1 } }, plain: { four: 0 } }} setConfig={vi.fn()} />,
|
|
1140
|
+
);
|
|
1141
|
+
expect(screen.getByRole('button', { name: 'Reset Modes' })).toBeDisabled();
|
|
1142
|
+
expect(screen.queryByRole('button', { name: 'Reset Plain' })).toBeNull();
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
it('writes the default of every changed value beneath the group and nothing else', () => {
|
|
1146
|
+
const setConfig = vi.fn();
|
|
1147
|
+
render(
|
|
1148
|
+
<ControlPanel schema={schema} config={{ name: 'b', modes: { one: 2, two: 0, inner: { three: 5 } }, plain: { four: 3 } }} setConfig={setConfig} />,
|
|
1149
|
+
);
|
|
1150
|
+
fireEvent.click(screen.getByRole('button', { name: 'Reset Modes' }));
|
|
1151
|
+
expect(setConfig.mock.calls).toEqual([
|
|
1152
|
+
['modes.one', 0],
|
|
1153
|
+
['modes.inner.three', 1],
|
|
1154
|
+
]);
|
|
1155
|
+
});
|
|
1156
|
+
});
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { getAlpha01, isBuiltinToolPref, toHex8, withAlpha01 } from '@weasel-js/core';
|
|
2
2
|
import {
|
|
3
|
+
Button,
|
|
3
4
|
DialogRow,
|
|
4
5
|
isPrefLeaf,
|
|
5
6
|
ListEditor,
|
|
6
7
|
type PrefGroup,
|
|
7
8
|
type PrefLeaf,
|
|
8
9
|
type PropertyAlign,
|
|
9
|
-
type PropertyControlProps,
|
|
10
10
|
PropertyControl,
|
|
11
|
+
type PropertyControlProps,
|
|
11
12
|
type PropertyDensity,
|
|
12
13
|
PropertyField,
|
|
13
14
|
PropertyGroup,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
PropertyRow,
|
|
18
19
|
type PropertyRowLayout,
|
|
19
20
|
prefFieldProps,
|
|
21
|
+
ResetIcon,
|
|
20
22
|
type StanceProps,
|
|
21
23
|
} from '@weasel-js/ui';
|
|
22
24
|
import { Fragment, type ReactNode, useEffect, useMemo, useRef, useState } from 'react';
|
|
@@ -26,7 +28,7 @@ import { fromConfigFields } from '../config/fromConfigField';
|
|
|
26
28
|
import { schemaNodeAtPath, valueAtPath } from '../config/path';
|
|
27
29
|
import type { ControlRenderer, ResolvedConfig, SectionSpec } from '../config/types';
|
|
28
30
|
import { isLeafVisible } from '../config/visible';
|
|
29
|
-
import { summarizeValue } from './inDialog';
|
|
31
|
+
import { inDialog, summarizeValue } from './inDialog';
|
|
30
32
|
import type { ConfigField } from './types';
|
|
31
33
|
|
|
32
34
|
/** How a panel packs its rows into the two-column property grid.
|
|
@@ -117,6 +119,45 @@ export interface ControlPanelProps<TC extends Record<string, unknown>> extends S
|
|
|
117
119
|
|
|
118
120
|
const NO_AUTO: ReadonlySet<string> = new Set();
|
|
119
121
|
|
|
122
|
+
/** Every leaf beneath a group, as its dotted path and its default. */
|
|
123
|
+
function leavesUnder(group: PrefGroup, at: string): [string, unknown][] {
|
|
124
|
+
return Object.entries(group.children).flatMap(([key, child]) => {
|
|
125
|
+
const path = at === '' ? key : `${at}.${key}`;
|
|
126
|
+
return isPrefLeaf(child) ? [[path, child.default] as [string, unknown]] : leavesUnder(child, path);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The heading button of a `.resettable()` group: writes each changed leaf's
|
|
131
|
+
* default, and is disabled while nothing has changed. */
|
|
132
|
+
function ResetGroup({
|
|
133
|
+
group,
|
|
134
|
+
path,
|
|
135
|
+
config,
|
|
136
|
+
setConfig,
|
|
137
|
+
}: {
|
|
138
|
+
group: PrefGroup;
|
|
139
|
+
path: string;
|
|
140
|
+
config: Record<string, unknown>;
|
|
141
|
+
setConfig: (path: string, value: unknown) => void;
|
|
142
|
+
}) {
|
|
143
|
+
const changed = leavesUnder(group, path).filter(([p, d]) => !Object.is(valueAtPath(config, p), d));
|
|
144
|
+
return (
|
|
145
|
+
<Button
|
|
146
|
+
iconOnly
|
|
147
|
+
variant="ghost"
|
|
148
|
+
size="sm"
|
|
149
|
+
ariaLabel={`Reset ${group.name}`}
|
|
150
|
+
tooltip={`Reset ${group.name} to defaults`}
|
|
151
|
+
disabled={changed.length === 0}
|
|
152
|
+
onClick={() => {
|
|
153
|
+
for (const [p, d] of changed) setConfig(p, d);
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
<ResetIcon />
|
|
157
|
+
</Button>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
120
161
|
/** Render an instrument's config schema as a stack of controls, each writing
|
|
121
162
|
* back through `setConfig`. Built on the property rows, so a lab's controls
|
|
122
163
|
* are the same aligned, themed rows the rest of the kit uses. */
|
|
@@ -224,6 +265,11 @@ export function ControlPanel<TC extends Record<string, unknown>>({
|
|
|
224
265
|
// two-column grid leaves it laying its own rows out inside a cell,
|
|
225
266
|
// which overlaps them.
|
|
226
267
|
span
|
|
268
|
+
actions={
|
|
269
|
+
resolved.resettable?.has(path) ? (
|
|
270
|
+
<ResetGroup group={found} path={path} config={config as Record<string, unknown>} setConfig={setConfig} />
|
|
271
|
+
) : undefined
|
|
272
|
+
}
|
|
227
273
|
{...fold(path, undefined, rows.grid)}
|
|
228
274
|
>
|
|
229
275
|
{body(found, path, rows)}
|
|
@@ -243,7 +289,13 @@ export function ControlPanel<TC extends Record<string, unknown>>({
|
|
|
243
289
|
if (pair === undefined) return undefined;
|
|
244
290
|
if (!isLeafVisible(resolved, path, config as Record<string, unknown>, showHidden))
|
|
245
291
|
return undefined;
|
|
246
|
-
if (
|
|
292
|
+
if (
|
|
293
|
+
renderers?.[path] ??
|
|
294
|
+
resolved.renderers[path] ??
|
|
295
|
+
resolved.dialogs[path] ??
|
|
296
|
+
renderers?.[found.kind]
|
|
297
|
+
)
|
|
298
|
+
return undefined;
|
|
247
299
|
if (!isBuiltinToolPref(found)) return undefined;
|
|
248
300
|
if (found.kind === 'paint' || found.kind === 'object' || isSliderLeaf(found)) return undefined;
|
|
249
301
|
return { leaf: found, pair };
|
|
@@ -405,8 +457,13 @@ function ControlRow<TC extends Record<string, unknown>>({
|
|
|
405
457
|
const autoProps = { auto: isAutoRow, onAutoChange };
|
|
406
458
|
|
|
407
459
|
// Most specific wins, and within a tier the lab's entry beats the
|
|
408
|
-
// instrument's: controls[path] -> node .render -> controls[kind] -> built-in.
|
|
409
|
-
const
|
|
460
|
+
// instrument's: controls[path] -> node .render/.dialog -> controls[kind] -> built-in.
|
|
461
|
+
const dialog = resolved.dialogs[path];
|
|
462
|
+
const custom =
|
|
463
|
+
renderers?.[path] ??
|
|
464
|
+
resolved.renderers[path] ??
|
|
465
|
+
(dialog ? inDialog(dialog.body, dialog) : undefined) ??
|
|
466
|
+
renderers?.[leaf.kind];
|
|
410
467
|
// A custom row places itself like any other; one that needs the full width
|
|
411
468
|
// says so with `<PropertyRow span>`, which is the same opt-out a built-in has.
|
|
412
469
|
if (custom) return custom({ path, pref: leaf, value, setValue: write, auto: isAutoRow, setAuto });
|
|
@@ -442,14 +499,15 @@ function ControlRow<TC extends Record<string, unknown>>({
|
|
|
442
499
|
}
|
|
443
500
|
|
|
444
501
|
const field = labField(leaf, value, write);
|
|
445
|
-
if (field === null)
|
|
502
|
+
if (field === null)
|
|
503
|
+
return <UnwiredRow label={label} kind={leaf.kind} description={description} />;
|
|
446
504
|
switch (field.kind) {
|
|
447
505
|
case 'string':
|
|
448
506
|
return <DebouncedTextRow leaf={leaf} field={field} span={wide} {...row} />;
|
|
449
507
|
case 'number':
|
|
450
508
|
return <PropertyField {...field} {...row} span={field.control === 'slider' && wide} />;
|
|
451
509
|
case 'enum':
|
|
452
|
-
return <PropertyField {...field} {...row} span={field.control === '
|
|
510
|
+
return <PropertyField {...field} {...row} span={field.control === 'toggle' && wide} />;
|
|
453
511
|
default:
|
|
454
512
|
return <PropertyField {...field} {...row} />;
|
|
455
513
|
}
|
|
@@ -479,9 +537,16 @@ function labField(
|
|
|
479
537
|
unit: extra<string>(leaf, 'suffix') ?? field.unit,
|
|
480
538
|
};
|
|
481
539
|
case 'boolean':
|
|
482
|
-
return {
|
|
540
|
+
return {
|
|
541
|
+
...field,
|
|
542
|
+
control: extra<string>(leaf, 'control') === 'switch' ? 'switch' : 'checkbox',
|
|
543
|
+
};
|
|
483
544
|
case 'enum':
|
|
484
|
-
|
|
545
|
+
// `.radio()` asks for every option at once, as segments.
|
|
546
|
+
return {
|
|
547
|
+
...field,
|
|
548
|
+
control: extra<string>(leaf, 'control') === 'radio' ? 'toggle' : 'select',
|
|
549
|
+
};
|
|
485
550
|
case 'string':
|
|
486
551
|
return {
|
|
487
552
|
...field,
|
|
@@ -605,7 +670,9 @@ function PairCell({
|
|
|
605
670
|
if (field === null) return null;
|
|
606
671
|
switch (field.kind) {
|
|
607
672
|
case 'string':
|
|
608
|
-
return
|
|
673
|
+
return (
|
|
674
|
+
<PropertyControl {...field} name={leaf.name} value={text.local} onChange={text.type} />
|
|
675
|
+
);
|
|
609
676
|
case 'number':
|
|
610
677
|
// The pair names the row, so a cell has no room for a unit.
|
|
611
678
|
return <PropertyControl {...field} name={leaf.name} control="input" unit={undefined} />;
|
|
@@ -618,7 +685,9 @@ function PairCell({
|
|
|
618
685
|
{...field}
|
|
619
686
|
name={leaf.name}
|
|
620
687
|
alpha={undefined}
|
|
621
|
-
onChange={(rgb: string) =>
|
|
688
|
+
onChange={(rgb: string) =>
|
|
689
|
+
write(field.alpha ? withAlpha01(rgb, getAlpha01(stored)) : rgb)
|
|
690
|
+
}
|
|
622
691
|
/>
|
|
623
692
|
);
|
|
624
693
|
}
|
|
@@ -69,6 +69,24 @@ describe('.dialog', () => {
|
|
|
69
69
|
render(<Panel schema={schema} />);
|
|
70
70
|
expect(screen.getByRole('button', { name: 'Edit Pts' }).textContent).toContain('3 points');
|
|
71
71
|
});
|
|
72
|
+
|
|
73
|
+
it('gives way to a later .render, and takes over from an earlier one', () => {
|
|
74
|
+
const schema = f.schema({
|
|
75
|
+
a: f
|
|
76
|
+
.string('x')
|
|
77
|
+
.dialog(() => null)
|
|
78
|
+
.render(() => <span>row a</span>),
|
|
79
|
+
b: f
|
|
80
|
+
.string('y')
|
|
81
|
+
.render(() => <span>row b</span>)
|
|
82
|
+
.dialog(() => null),
|
|
83
|
+
});
|
|
84
|
+
render(<Panel schema={schema} />);
|
|
85
|
+
expect(screen.getByText('row a')).toBeTruthy();
|
|
86
|
+
expect(screen.queryByRole('button', { name: 'Edit A' })).toBeNull();
|
|
87
|
+
expect(screen.queryByText('row b')).toBeNull();
|
|
88
|
+
expect(screen.getByRole('button', { name: 'Edit B' })).toBeTruthy();
|
|
89
|
+
});
|
|
72
90
|
});
|
|
73
91
|
|
|
74
92
|
describe('summarizeValue', () => {
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import { DialogRow } from '@weasel-js/ui';
|
|
2
|
-
import type {
|
|
3
|
-
import type { ControlRenderer } from '../config/types';
|
|
2
|
+
import type { ControlRenderer, InDialogOptions } from '../config/types';
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
export interface InDialogOptions {
|
|
7
|
-
/** What the row's button shows of the value. Defaults to a list joined with
|
|
8
|
-
* commas, or the value itself. */
|
|
9
|
-
summary?: (value: unknown) => ReactNode;
|
|
10
|
-
/** The dialog's heading. Defaults to the row's label. */
|
|
11
|
-
title?: ReactNode;
|
|
12
|
-
}
|
|
4
|
+
export type { InDialogOptions };
|
|
13
5
|
|
|
14
6
|
/** The one-line summary a dialog row shows when it is given none. */
|
|
15
7
|
export function summarizeValue(value: unknown): string {
|
package/src/index.test.ts
CHANGED
|
@@ -72,7 +72,7 @@ describe('surface, job and orbit entry points', () => {
|
|
|
72
72
|
expect(typeof kit.annotationsFromJSON).toBe('function');
|
|
73
73
|
expect(typeof kit.seenFrom).toBe('function');
|
|
74
74
|
expect(typeof kit.fracToWorld).toBe('function');
|
|
75
|
-
expect(typeof kit.
|
|
75
|
+
expect(typeof kit.CameraInput).toBe('function');
|
|
76
76
|
expect(typeof kit.useJob).toBe('function');
|
|
77
77
|
expect(typeof kit.as2DView).toBe('function');
|
|
78
78
|
expect(kit.SurfaceContext).toBeDefined();
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The property-panel family, LayerList, Input, Select, TokenPanel, ToggleBar and Button live in
|
|
1
|
+
// The property-panel family, DetailList, LayerList, Input, Select, TokenPanel, ToggleBar and Button live in
|
|
2
2
|
// `@weasel-js/ui`; labkit re-exports them so chrome built on labkit needs no
|
|
3
3
|
// direct ui dependency. Named, not `export *` — a star re-export of an external
|
|
4
4
|
// package emits no binding in the bundle.
|
|
@@ -10,6 +10,11 @@ export type {
|
|
|
10
10
|
ButtonVariant,
|
|
11
11
|
CurveFieldProps,
|
|
12
12
|
CurveMark,
|
|
13
|
+
DetailListLayout,
|
|
14
|
+
DetailListProps,
|
|
15
|
+
DetailListValues,
|
|
16
|
+
DetailRowProps,
|
|
17
|
+
DetailRowStatus,
|
|
13
18
|
IconProps,
|
|
14
19
|
InputProps,
|
|
15
20
|
JogProps,
|
|
@@ -41,6 +46,8 @@ export {
|
|
|
41
46
|
CloseIcon,
|
|
42
47
|
CollapseIcon,
|
|
43
48
|
CurveField,
|
|
49
|
+
DetailList,
|
|
50
|
+
DetailRow,
|
|
44
51
|
ExpandIcon,
|
|
45
52
|
Icon,
|
|
46
53
|
Input,
|
|
@@ -79,6 +86,7 @@ export type {
|
|
|
79
86
|
AnnotationsCapability,
|
|
80
87
|
AnnotationTarget,
|
|
81
88
|
AnnotationTargetInfo,
|
|
89
|
+
AnnotationToolId,
|
|
82
90
|
CaptureDeps,
|
|
83
91
|
CaptureOptions,
|
|
84
92
|
CaptureResult,
|
|
@@ -129,29 +137,37 @@ export {
|
|
|
129
137
|
export type { ContentSize, PaneSize } from './annotations/view';
|
|
130
138
|
export { fitView, fromWeaselView, toWeaselView } from './annotations/view';
|
|
131
139
|
export type {
|
|
140
|
+
CameraContextValue,
|
|
141
|
+
CameraRegistry,
|
|
142
|
+
CameraView,
|
|
132
143
|
CanvasLayerDescriptor,
|
|
133
144
|
CanvasStackContextValue,
|
|
134
145
|
CanvasStackProps,
|
|
135
146
|
CanvasStackSurface,
|
|
136
|
-
PanZoomHandlers,
|
|
137
147
|
StageProps,
|
|
138
|
-
UsePanZoomOptions,
|
|
139
148
|
ViewportSize,
|
|
140
149
|
WorldFrame,
|
|
141
150
|
WorldSpec,
|
|
142
151
|
ZoomAtOptions,
|
|
152
|
+
ZoomRange,
|
|
143
153
|
} from './canvas';
|
|
144
154
|
export {
|
|
145
155
|
applyCamera,
|
|
156
|
+
CameraContext,
|
|
157
|
+
CameraInput,
|
|
158
|
+
CameraPublishContext,
|
|
159
|
+
CameraRegistryContext,
|
|
160
|
+
CameraScope,
|
|
146
161
|
CanvasStack,
|
|
147
162
|
CanvasStackContext,
|
|
148
163
|
centerOn,
|
|
164
|
+
createCameraRegistry,
|
|
149
165
|
DEFAULT_FRAME,
|
|
150
166
|
fitStage,
|
|
151
167
|
resolveFrame,
|
|
152
168
|
Stage,
|
|
153
169
|
screenToWorld,
|
|
154
|
-
|
|
170
|
+
usePublishCamera,
|
|
155
171
|
worldToScreen,
|
|
156
172
|
zoomAt,
|
|
157
173
|
} from './canvas';
|
|
@@ -173,7 +189,6 @@ export {
|
|
|
173
189
|
ValueNode,
|
|
174
190
|
} from './config/builder';
|
|
175
191
|
export { fromConfigFields } from './config/fromConfigField';
|
|
176
|
-
export { type SectionTree, sectionTree } from './config/sectionTree';
|
|
177
192
|
export {
|
|
178
193
|
fillConfigDefaults,
|
|
179
194
|
hasConfigPath,
|
|
@@ -183,6 +198,7 @@ export {
|
|
|
183
198
|
} from './config/path';
|
|
184
199
|
export { resolveConfigSchema } from './config/resolve';
|
|
185
200
|
export { applyRules, builtinRules, titleCase } from './config/rules';
|
|
201
|
+
export { type SectionTree, sectionTree } from './config/sectionTree';
|
|
186
202
|
export type {
|
|
187
203
|
Annotations,
|
|
188
204
|
BranchAnnotations,
|
|
@@ -198,12 +214,14 @@ export type {
|
|
|
198
214
|
ConfigSchema,
|
|
199
215
|
ConfigShape,
|
|
200
216
|
ControlRenderer,
|
|
217
|
+
DialogSpec,
|
|
201
218
|
EntryValue,
|
|
202
219
|
InferConfig,
|
|
203
220
|
LeafPatch,
|
|
204
221
|
NodeOptions,
|
|
205
222
|
NodeValue,
|
|
206
223
|
ResolvedConfig,
|
|
224
|
+
SectionOption,
|
|
207
225
|
SectionSpec,
|
|
208
226
|
ValueAtPath,
|
|
209
227
|
} from './config/types';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { render } from '@testing-library/react';
|
|
2
|
+
import { createPointerStore } from '@weasel-js/core';
|
|
2
3
|
import type { ReactElement } from 'react';
|
|
3
4
|
import { describe, expect, it, vi } from 'vitest';
|
|
4
5
|
import { DEFAULT_VIEW } from '../state/view';
|
|
@@ -94,6 +95,7 @@ describe('SineWave instrument smoke test', () => {
|
|
|
94
95
|
setZoom: vi.fn(),
|
|
95
96
|
activeToolId: null,
|
|
96
97
|
visibleLayers: [],
|
|
98
|
+
pointer: createPointerStore(),
|
|
97
99
|
},
|
|
98
100
|
emit: vi.fn(),
|
|
99
101
|
};
|
package/src/instrument/types.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PointerContextValue } from '@weasel-js/core';
|
|
1
2
|
import type { ReactNode } from 'react';
|
|
2
3
|
import type { AnnotationsCapability } from '../annotations/types';
|
|
3
4
|
import type { ViewportSize, WorldSpec } from '../canvas/worldSpec';
|
|
@@ -33,6 +34,14 @@ export interface RenderContext<TS = unknown, TC = unknown> {
|
|
|
33
34
|
* hidden layer's `draw`, so this is the only way to know what was
|
|
34
35
|
* painted — a legend listing rows the view did not draw needs it. */
|
|
35
36
|
visibleLayers: readonly string[];
|
|
37
|
+
/**
|
|
38
|
+
* Where the pointer is over this trial, in the instrument's world:
|
|
39
|
+
* `get()` answers `{ worldX, worldY, viewId }`, where `viewId` is
|
|
40
|
+
* `'stage'` over the trial's own camera and `'overview'` over its
|
|
41
|
+
* `<TrialOverview>`, or `null` when it is over neither. A key handler
|
|
42
|
+
* reading it works over either view.
|
|
43
|
+
*/
|
|
44
|
+
pointer: PointerContextValue;
|
|
36
45
|
};
|
|
37
46
|
emit: (event: string) => void;
|
|
38
47
|
/** Present only when the instrument declares a `job`. */
|
|
@@ -61,7 +70,7 @@ export interface CanvasCapability<TS = unknown, TC = unknown> {
|
|
|
61
70
|
* size, so an instrument can frame content it can only place in terms of
|
|
62
71
|
* the viewport; until then the trial's view is `null`. */
|
|
63
72
|
initialView?: ViewTransform | ((size: ViewportSize) => ViewTransform);
|
|
64
|
-
/** Widens
|
|
73
|
+
/** Widens the camera's default zoom clamp; the opening zoom stays reachable
|
|
65
74
|
* regardless of these. */
|
|
66
75
|
minZoom?: number;
|
|
67
76
|
maxZoom?: number;
|
|
@@ -19,12 +19,21 @@ const backdrop = (container: HTMLElement) =>
|
|
|
19
19
|
|
|
20
20
|
describe('<Lab nebula>', () => {
|
|
21
21
|
it('draws one blob per literal color', () => {
|
|
22
|
-
const { container } = render(
|
|
22
|
+
const { container } = render(
|
|
23
|
+
<Lab
|
|
24
|
+
instruments={[bare]}
|
|
25
|
+
defaultInstrument="Bare"
|
|
26
|
+
mode="dark"
|
|
27
|
+
nebula={['#ff0000', '#00ff00']}
|
|
28
|
+
/>,
|
|
29
|
+
);
|
|
23
30
|
expect(backdrop(container).match(/radial-gradient/g)).toHaveLength(3);
|
|
24
31
|
});
|
|
25
32
|
|
|
26
33
|
it('resolves a theme ramp against the lab’s theme', () => {
|
|
27
|
-
const { container } = render(
|
|
34
|
+
const { container } = render(
|
|
35
|
+
<Lab instruments={[bare]} defaultInstrument="Bare" mode="dark" nebula={{ ramp: 'swatch' }} />,
|
|
36
|
+
);
|
|
28
37
|
const sky = resolveTheme(interstellarTheme, { mode: 'dark' })['--wzl-swatch-sky'];
|
|
29
38
|
expect(backdrop(container)).toContain(sky);
|
|
30
39
|
});
|
package/src/lab/Lab.test.tsx
CHANGED
|
@@ -165,7 +165,9 @@ describe('<Lab>', () => {
|
|
|
165
165
|
storageKey: 's',
|
|
166
166
|
});
|
|
167
167
|
await waitFor(() => expect(labRef?.trials[0]).toBeDefined());
|
|
168
|
-
expect((labRef?.trials[0]?.state as { seen: Set<string> })
|
|
168
|
+
expect((labRef?.trials[0]?.state as { seen: Set<string> } | undefined)?.seen).toEqual(
|
|
169
|
+
new Set(['a']),
|
|
170
|
+
);
|
|
169
171
|
});
|
|
170
172
|
});
|
|
171
173
|
|