@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.
Files changed (67) hide show
  1. package/dist/chrome/index.js +4 -4
  2. package/dist/{chunk-GAHELLJT.js → chunk-RODIY7HI.js} +7 -7
  3. package/dist/{chunk-GAHELLJT.js.map → chunk-RODIY7HI.js.map} +1 -1
  4. package/dist/{chunk-4KSC7NXL.js → chunk-U6UQVGFP.js} +1984 -1892
  5. package/dist/chunk-U6UQVGFP.js.map +1 -0
  6. package/dist/chunk-WFALF2X2.js +302 -0
  7. package/dist/chunk-WFALF2X2.js.map +1 -0
  8. package/dist/{chunk-JFOJESJF.js → chunk-WWJDPNZZ.js} +22 -22
  9. package/dist/chunk-WWJDPNZZ.js.map +1 -0
  10. package/dist/{chunk-CEGFXTBD.js → chunk-XRX5S5NT.js} +4 -4
  11. package/dist/{chunk-CEGFXTBD.js.map → chunk-XRX5S5NT.js.map} +1 -1
  12. package/dist/{chunk-FB4MIZOO.js → chunk-XZDCEMEL.js} +6 -6
  13. package/dist/chunk-XZDCEMEL.js.map +1 -0
  14. package/dist/controls/index.d.ts +55 -3
  15. package/dist/controls/index.js +3 -2
  16. package/dist/fonts/oswald-tabular-digits.woff2 +0 -0
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +135 -134
  19. package/dist/index.js.map +1 -1
  20. package/dist/layers/index.js +1 -1
  21. package/dist/passthrough/weasel-ui.js +1 -1
  22. package/dist/primitives/index.js +3 -3
  23. package/dist/styles.css +228 -11
  24. package/package.json +11 -11
  25. package/src/annotations/capture.browser.test.ts +9 -3
  26. package/src/canvas/Stage.test.tsx +34 -25
  27. package/src/chrome/labRegions.test.tsx +37 -26
  28. package/src/chrome/regions/PaletteRegion.test.tsx +2 -2
  29. package/src/controls/ControlMatrix.less +245 -0
  30. package/src/controls/ControlMatrix.stories.less +13 -0
  31. package/src/controls/ControlMatrix.stories.tsx +135 -0
  32. package/src/controls/ControlMatrix.test.tsx +206 -0
  33. package/src/controls/ControlMatrix.tsx +446 -0
  34. package/src/controls/ControlPanel.tsx +4 -2
  35. package/src/controls/index.ts +6 -0
  36. package/src/dragdrop/dragDrop.test.tsx +34 -33
  37. package/src/index.ts +6 -0
  38. package/src/lab/Lab.chrome.test.tsx +15 -10
  39. package/src/lab/Lab.nebula.test.tsx +5 -5
  40. package/src/lab/Lab.surface.test.tsx +30 -21
  41. package/src/lab/Lab.test.tsx +75 -74
  42. package/src/lab/Lab.undock.test.tsx +15 -10
  43. package/src/lab/LabHeader.test.tsx +15 -14
  44. package/src/lab/LabPalette.test.tsx +12 -7
  45. package/src/lab/LabShell.less +1 -2
  46. package/src/lab/LabZoom.test.tsx +55 -44
  47. package/src/lab/Workspace.surface.test.tsx +16 -13
  48. package/src/lab/Workspace.test.tsx +32 -27
  49. package/src/overview/OverviewMarks.test.tsx +9 -8
  50. package/src/overview/TrialOverview.test.tsx +18 -17
  51. package/src/primitives/FpsMeter.less +1 -2
  52. package/src/primitives/JobProgress.less +1 -2
  53. package/src/primitives/ScaleIndicator.less +1 -1
  54. package/src/primitives/StatusBar.less +1 -1
  55. package/src/specimen/Specimen.less +1 -0
  56. package/src/styles.less +3 -0
  57. package/src/trial/Trial.annotations.persist.test.tsx +20 -9
  58. package/src/trial/Trial.annotations.test.tsx +46 -41
  59. package/src/trial/Trial.canvas.test.tsx +21 -17
  60. package/src/trial/Trial.config.test.tsx +34 -27
  61. package/src/trial/Trial.loupe.test.tsx +27 -18
  62. package/src/trial/Trial.targets.test.tsx +9 -8
  63. package/src/trial/Trial.test.tsx +51 -46
  64. package/src/trial/Trial.trialId.test.tsx +7 -6
  65. package/dist/chunk-4KSC7NXL.js.map +0 -1
  66. package/dist/chunk-FB4MIZOO.js.map +0 -1
  67. package/dist/chunk-JFOJESJF.js.map +0 -1
@@ -1,5 +1,6 @@
1
- import { act, fireEvent, render, screen } from '@testing-library/react';
1
+ import { act, fireEvent, screen } from '@testing-library/react';
2
2
  import userEvent from '@testing-library/user-event';
3
+ import { renderSettled } from '@weasel-js/react/testing/renderSettled';
3
4
  import { beforeAll, describe, expect, it, vi } from 'vitest';
4
5
  import { defineInstrument } from '../instrument/defineInstrument';
5
6
  import { Lab } from '../lab/Lab';
@@ -53,27 +54,29 @@ function pointAt(host: Element, x: number, y: number): void {
53
54
  }
54
55
 
55
56
  describe('a lens the instrument mounts', () => {
56
- it('gives a trial a toolbar toggle, and no lens until it is on', () => {
57
- const { container } = render(
57
+ it('gives a trial a toolbar toggle, and no lens until it is on', async () => {
58
+ const { container } = await renderSettled(
58
59
  <Lab instruments={[canvasInstrument]} defaultInstrument="Drawn" />,
59
60
  );
60
61
  expect(screen.getByRole('button', { name: 'Loupe' })).toHaveAttribute('aria-pressed', 'false');
61
62
  expect(lens(container)).toBeNull();
62
63
  });
63
64
 
64
- it('offers nothing to an instrument that mounts none', () => {
65
- render(<Lab instruments={[noLoupe]} defaultInstrument="Plain" />);
65
+ it('offers nothing to an instrument that mounts none', async () => {
66
+ await renderSettled(<Lab instruments={[noLoupe]} defaultInstrument="Plain" />);
66
67
  expect(screen.queryByRole('button', { name: 'Loupe' })).toBeNull();
67
68
  });
68
69
 
69
- it('drops out of a trial that suppresses it', () => {
70
- render(<Lab instruments={[canvasInstrument]} defaultInstrument="Drawn" suppress={['loupe']} />);
70
+ it('drops out of a trial that suppresses it', async () => {
71
+ await renderSettled(
72
+ <Lab instruments={[canvasInstrument]} defaultInstrument="Drawn" suppress={['loupe']} />,
73
+ );
71
74
  expect(screen.queryByRole('button', { name: 'Loupe' })).toBeNull();
72
75
  });
73
76
 
74
77
  it('raises the lens over a canvas stack once it is on and aimed', async () => {
75
78
  const user = userEvent.setup();
76
- const { container } = render(
79
+ const { container } = await renderSettled(
77
80
  <Lab instruments={[canvasInstrument]} defaultInstrument="Drawn" />,
78
81
  );
79
82
  await user.click(screen.getByRole('button', { name: 'Loupe' }));
@@ -95,7 +98,9 @@ describe('a lens the instrument mounts', () => {
95
98
 
96
99
  it('asks a DOM instrument to draw itself again at the magnified camera', async () => {
97
100
  const user = userEvent.setup();
98
- const { container } = render(<Lab instruments={[domInstrument]} defaultInstrument="Written" />);
101
+ const { container } = await renderSettled(
102
+ <Lab instruments={[domInstrument]} defaultInstrument="Written" />,
103
+ );
99
104
  await user.click(screen.getByRole('button', { name: 'Loupe' }));
100
105
 
101
106
  const host = container.querySelector('.lk-loupe-host');
@@ -108,12 +113,14 @@ describe('a lens the instrument mounts', () => {
108
113
  expect(screen.getByTestId('body')).toBeInTheDocument();
109
114
  });
110
115
 
111
- it('wraps nothing for an instrument that mounts no lens', () => {
112
- const { container } = render(<Lab instruments={[noLoupe]} defaultInstrument="Plain" />);
116
+ it('wraps nothing for an instrument that mounts no lens', async () => {
117
+ const { container } = await renderSettled(
118
+ <Lab instruments={[noLoupe]} defaultInstrument="Plain" />,
119
+ );
113
120
  expect(container.querySelector('.lk-loupe-host')).toBeNull();
114
121
  });
115
122
 
116
- it('raises a lens told `enabled` without the trial toggle', () => {
123
+ it('raises a lens told `enabled` without the trial toggle', async () => {
117
124
  const always = defineInstrument<Record<string, never>, Record<string, never>>({
118
125
  name: 'Always',
119
126
  defaultConfig: () => ({}),
@@ -121,7 +128,9 @@ describe('a lens the instrument mounts', () => {
121
128
  render: () => <TrialLoupe enabled />,
122
129
  canvas: { layers: [{ id: 'main', draw: () => undefined }] },
123
130
  });
124
- const { container } = render(<Lab instruments={[always]} defaultInstrument="Always" />);
131
+ const { container } = await renderSettled(
132
+ <Lab instruments={[always]} defaultInstrument="Always" />,
133
+ );
125
134
  const stack = container.querySelector('.lk-canvas-stack');
126
135
  if (!stack) throw new Error('no canvas stack');
127
136
  pointAt(stack, 60, 40);
@@ -130,7 +139,7 @@ describe('a lens the instrument mounts', () => {
130
139
 
131
140
  it('puts the lens away again when the toggle goes off', async () => {
132
141
  const user = userEvent.setup();
133
- const { container } = render(
142
+ const { container } = await renderSettled(
134
143
  <Lab instruments={[canvasInstrument]} defaultInstrument="Drawn" />,
135
144
  );
136
145
  const toggle = screen.getByRole('button', { name: 'Loupe' });
@@ -148,7 +157,7 @@ describe('a lens the instrument mounts', () => {
148
157
  describe('the loupe and the trial camera share one dispatcher', () => {
149
158
  const frame = () => new Promise<void>((r) => requestAnimationFrame(() => r()));
150
159
 
151
- function mountDrawn() {
160
+ async function mountDrawn() {
152
161
  const zooms: number[] = [];
153
162
  const drawn = defineInstrument<Record<string, never>, Record<string, never>>({
154
163
  name: 'Zoomed',
@@ -166,7 +175,7 @@ describe('the loupe and the trial camera share one dispatcher', () => {
166
175
  ],
167
176
  },
168
177
  });
169
- const r = render(<Lab instruments={[drawn]} defaultInstrument="Zoomed" />);
178
+ const r = await renderSettled(<Lab instruments={[drawn]} defaultInstrument="Zoomed" />);
170
179
  const stack = r.container.querySelector('.lk-canvas-stack');
171
180
  if (!stack) throw new Error('no canvas stack');
172
181
  return { ...r, stack, zooms };
@@ -185,7 +194,7 @@ describe('the loupe and the trial camera share one dispatcher', () => {
185
194
  };
186
195
 
187
196
  it('zooms the camera while the lens is down', async () => {
188
- const h = mountDrawn();
197
+ const h = await mountDrawn();
189
198
  await act(async () => {
190
199
  await frame();
191
200
  });
@@ -198,7 +207,7 @@ describe('the loupe and the trial camera share one dispatcher', () => {
198
207
 
199
208
  it('gives the wheel to the lens while it is up, and leaves the camera alone', async () => {
200
209
  const user = userEvent.setup();
201
- const h = mountDrawn();
210
+ const h = await mountDrawn();
202
211
  await user.click(screen.getByRole('button', { name: 'Loupe' }));
203
212
  pointAt(h.stack, 60, 40);
204
213
  expect(lens(h.container)).not.toBeNull();
@@ -4,7 +4,8 @@
4
4
  * trial open, a `targets` that ignored the trial it was asked about would look
5
5
  * identical.
6
6
  */
7
- import { act, fireEvent, render, screen } from '@testing-library/react';
7
+ import { act, fireEvent, screen } from '@testing-library/react';
8
+ import { renderSettled } from '@weasel-js/react/testing/renderSettled';
8
9
  import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
9
10
  import type { AnnotationTarget } from '../annotations/types';
10
11
  import { defineInstrument } from '../instrument/defineInstrument';
@@ -63,13 +64,13 @@ function CaptureLab() {
63
64
  );
64
65
  }
65
66
 
66
- function mountTwoTrials(): [string, string] {
67
- render(
67
+ async function mountTwoTrials(): Promise<[string, string]> {
68
+ await renderSettled(
68
69
  <Lab instruments={[subject]} defaultInstrument="Subject">
69
70
  <CaptureLab />
70
71
  </Lab>,
71
72
  );
72
- act(() => labRef?.addTrial('Subject'));
73
+ await act(async () => labRef?.addTrial('Subject'));
73
74
  const ids = labRef?.trials.map((t) => t.id) ?? [];
74
75
  const [a, b] = ids;
75
76
  if (!a || !b) throw new Error('expected two trials');
@@ -82,13 +83,13 @@ describe('annotations.targets', () => {
82
83
  labRef = null;
83
84
  });
84
85
 
85
- it('is told which trial is asking', () => {
86
- const [a, b] = mountTwoTrials();
86
+ it('is told which trial is asking', async () => {
87
+ const [a, b] = await mountTwoTrials();
87
88
  expect(new Set(asked.map((t) => t.id))).toEqual(new Set([a, b]));
88
89
  });
89
90
 
90
- it("is told that trial's own view, not the other's", () => {
91
- const [a, b] = mountTwoTrials();
91
+ it("is told that trial's own view, not the other's", async () => {
92
+ const [a, b] = await mountTwoTrials();
92
93
  fireEvent.click(screen.getByTestId(`zoom-${a}`));
93
94
  expect(lastAsk(a).view).toEqual({ zoom: 3, pan: { x: 0, y: 0 } });
94
95
  expect(lastAsk(b).view).toEqual({ zoom: 1, pan: { x: 0, y: 0 } });
@@ -1,4 +1,5 @@
1
- import { fireEvent, render, screen, within } from '@testing-library/react';
1
+ import { fireEvent, screen, within } from '@testing-library/react';
2
+ import { renderSettled } from '@weasel-js/react/testing/renderSettled';
2
3
  import { type ReactNode, useState } from 'react';
3
4
  import { describe, expect, it, vi } from 'vitest';
4
5
  import { useStore } from 'zustand/react';
@@ -102,19 +103,19 @@ function ChromeFromStore({
102
103
  }
103
104
 
104
105
  describe('<TrialChrome>', () => {
105
- it('renders children in the content area', () => {
106
- render(<ChromeHarness />);
106
+ it('renders children in the content area', async () => {
107
+ await renderSettled(<ChromeHarness />);
107
108
  expect(screen.getByTestId('content')).toBeInTheDocument();
108
109
  });
109
110
 
110
- it('renders the built-in trial actions', () => {
111
- render(<ChromeHarness />);
111
+ it('renders the built-in trial actions', async () => {
112
+ await renderSettled(<ChromeHarness />);
112
113
  expect(screen.getByRole('button', { name: 'Close trial' })).toBeInTheDocument();
113
114
  expect(screen.getByRole('button', { name: 'Clone trial' })).toBeInTheDocument();
114
115
  });
115
116
 
116
- it('puts clone, reset and snapshot at the end of the title bar, beside close', () => {
117
- const { container } = render(<ChromeHarness />);
117
+ it('puts clone, reset and snapshot at the end of the title bar, beside close', async () => {
118
+ const { container } = await renderSettled(<ChromeHarness />);
118
119
  const titlebar = container.querySelector('.lk-trial__titlebar');
119
120
  if (!titlebar) throw new Error('no title bar');
120
121
  expect(titlebar).toContainElement(screen.getByRole('button', { name: 'Clone trial' }));
@@ -125,15 +126,15 @@ describe('<TrialChrome>', () => {
125
126
  expect(labels).toEqual(['Clone trial', 'Reset trial', 'Save snapshot', 'Close trial']);
126
127
  });
127
128
 
128
- it('disables close on the last trial', () => {
129
- render(<ChromeHarness isLastTrial />);
129
+ it('disables close on the last trial', async () => {
130
+ await renderSettled(<ChromeHarness isLastTrial />);
130
131
  expect(screen.getByRole('button', { name: /close/i })).toBeDisabled();
131
132
  });
132
133
 
133
- it('omits undo and redo unless the instrument declares undo', () => {
134
- render(<ChromeHarness />);
134
+ it('omits undo and redo unless the instrument declares undo', async () => {
135
+ await renderSettled(<ChromeHarness />);
135
136
  expect(screen.queryByRole('button', { name: 'Undo' })).toBeNull();
136
- render(
137
+ await renderSettled(
137
138
  <ChromeHarness
138
139
  instrument={{ ...stubInstrument, undo: {} }}
139
140
  undoBindings={{ canUndo: true, canRedo: false, undo: vi.fn(), redo: vi.fn() }}
@@ -143,7 +144,7 @@ describe('<TrialChrome>', () => {
143
144
  expect(screen.getByRole('button', { name: 'Redo' })).toBeDisabled();
144
145
  });
145
146
 
146
- it('offers the snapshot loader once a snapshot exists', () => {
147
+ it('offers the snapshot loader once a snapshot exists', async () => {
147
148
  const snapshot: SavedSnapshot = {
148
149
  id: 's1',
149
150
  name: 'First',
@@ -153,17 +154,19 @@ describe('<TrialChrome>', () => {
153
154
  state: {},
154
155
  savedAt: 1,
155
156
  };
156
- render(<ChromeHarness labOverrides={{ savedSnapshots: [snapshot] }} />);
157
+ await renderSettled(<ChromeHarness labOverrides={{ savedSnapshots: [snapshot] }} />);
157
158
  expect(screen.getByRole('button', { name: /load snapshot/i })).toBeInTheDocument();
158
159
  });
159
160
 
160
- it('renders a consumer contribution alongside the built-ins', () => {
161
- render(<ChromeHarness chrome={[{ id: 'mine', region: 'status', item: { text: 'ready' } }]} />);
161
+ it('renders a consumer contribution alongside the built-ins', async () => {
162
+ await renderSettled(
163
+ <ChromeHarness chrome={[{ id: 'mine', region: 'status', item: { text: 'ready' } }]} />,
164
+ );
162
165
  expect(screen.getByText('ready')).toBeInTheDocument();
163
166
  });
164
167
 
165
- it('leads the title bar with a contribution that does not set `end`', () => {
166
- const { container } = render(
168
+ it('leads the title bar with a contribution that does not set `end`', async () => {
169
+ const { container } = await renderSettled(
167
170
  <ChromeHarness
168
171
  chrome={[
169
172
  {
@@ -183,8 +186,8 @@ describe('<TrialChrome>', () => {
183
186
  expect(lead.compareDocumentPosition(heading) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
184
187
  });
185
188
 
186
- it('reads the instrument name until a contribution calls setTitle', () => {
187
- render(
189
+ it('reads the instrument name until a contribution calls setTitle', async () => {
190
+ await renderSettled(
188
191
  <ChromeHarness
189
192
  chrome={[
190
193
  {
@@ -205,9 +208,9 @@ describe('<TrialChrome>', () => {
205
208
  expect(screen.getByRole('region', { name: 'Trial Sprocket 7' })).toBeInTheDocument();
206
209
  });
207
210
 
208
- it("reads the instrument's title over its name, and returns to it on setTitle(null)", () => {
211
+ it("reads the instrument's title over its name, and returns to it on setTitle(null)", async () => {
209
212
  const titled: Instrument = { ...stubInstrument, title: 'Stub / Default' };
210
- render(
213
+ await renderSettled(
211
214
  <ChromeHarness
212
215
  instrument={titled}
213
216
  chrome={[
@@ -232,9 +235,9 @@ describe('<TrialChrome>', () => {
232
235
  expect(screen.getByRole('region', { name: 'Trial Stub / Default' })).toBeInTheDocument();
233
236
  });
234
237
 
235
- it('Cmd+S triggers saveSnapshot', () => {
238
+ it('Cmd+S triggers saveSnapshot', async () => {
236
239
  const saveSnapshot = vi.fn();
237
- render(<ChromeHarness labOverrides={{ saveSnapshot }} />);
240
+ await renderSettled(<ChromeHarness labOverrides={{ saveSnapshot }} />);
238
241
  const region = screen.getByRole('region', { name: /trial/i });
239
242
  fireEvent.keyDown(region, { key: 's', metaKey: true });
240
243
  expect(saveSnapshot).toHaveBeenCalledWith('ws-1', undefined);
@@ -242,30 +245,32 @@ describe('<TrialChrome>', () => {
242
245
  });
243
246
 
244
247
  describe('chrome regions in a mounted lab', () => {
245
- function renderLabWith(instrument: Instrument) {
246
- return render(<Lab title="T" instruments={[instrument]} defaultInstrument={instrument.name} />);
248
+ async function renderLabWith(instrument: Instrument) {
249
+ return renderSettled(
250
+ <Lab title="T" instruments={[instrument]} defaultInstrument={instrument.name} />,
251
+ );
247
252
  }
248
253
 
249
- it('puts zoom in the viewport region and not in the toolbar', () => {
250
- renderLabWith({ ...stubInstrument, canvas: { layers: [] }, undo: {} });
254
+ it('puts zoom in the viewport region and not in the toolbar', async () => {
255
+ await renderLabWith({ ...stubInstrument, canvas: { layers: [] }, undo: {} });
251
256
  const toolbar = document.querySelector('.lk-trial__toolbar') as HTMLElement;
252
257
  const viewport = document.querySelector('.lk-viewport-controls') as HTMLElement;
253
258
  expect(within(viewport).getByRole('button', { name: 'Zoom in' })).toBeInTheDocument();
254
259
  expect(within(toolbar).queryByRole('button', { name: 'Zoom in' })).toBeNull();
255
260
  });
256
261
 
257
- it('renders no undo group for an instrument that does not declare undo', () => {
258
- renderLabWith({ ...stubInstrument, canvas: { layers: [] } });
262
+ it('renders no undo group for an instrument that does not declare undo', async () => {
263
+ await renderLabWith({ ...stubInstrument, canvas: { layers: [] } });
259
264
  expect(screen.queryByRole('button', { name: 'Undo' })).toBeNull();
260
265
  });
261
266
 
262
- it('renders no viewport region for an instrument with no canvas', () => {
263
- renderLabWith(stubInstrument);
267
+ it('renders no viewport region for an instrument with no canvas', async () => {
268
+ await renderLabWith(stubInstrument);
264
269
  expect(document.querySelector('.lk-viewport-controls')).toBeNull();
265
270
  });
266
271
 
267
- it('Lab provides context for nested TrialChrome', () => {
268
- renderLabWith(stubInstrument);
272
+ it('Lab provides context for nested TrialChrome', async () => {
273
+ await renderLabWith(stubInstrument);
269
274
  expect(document.querySelector('.lk-lab')).toBeTruthy();
270
275
  });
271
276
  });
@@ -291,46 +296,46 @@ describe('<TrialChrome> — state that outlives a mount', () => {
291
296
  },
292
297
  ];
293
298
 
294
- it('keeps a title across a remount', () => {
299
+ it('keeps a title across a remount', async () => {
295
300
  const store = makeChromeStore();
296
- const first = render(<ChromeHarness store={store} chrome={renameChrome} />);
301
+ const first = await renderSettled(<ChromeHarness store={store} chrome={renameChrome} />);
297
302
  fireEvent.click(screen.getByRole('button', { name: 'Rename' }));
298
303
  first.unmount();
299
304
 
300
- render(<ChromeHarness store={store} chrome={renameChrome} />);
305
+ await renderSettled(<ChromeHarness store={store} chrome={renameChrome} />);
301
306
  expect(screen.getByRole('region', { name: 'Trial Sprocket 7' })).toBeInTheDocument();
302
307
  });
303
308
 
304
- it('keeps a folded sidebar section across a remount', () => {
309
+ it('keeps a folded sidebar section across a remount', async () => {
305
310
  const store = makeChromeStore();
306
- const first = render(<ChromeHarness store={store} chrome={sectionChrome} />);
311
+ const first = await renderSettled(<ChromeHarness store={store} chrome={sectionChrome} />);
307
312
  expect(screen.getByTestId('notes-body')).toBeInTheDocument();
308
313
  fireEvent.click(screen.getByRole('button', { name: 'Notes' }));
309
314
  expect(screen.queryByTestId('notes-body')).toBeNull();
310
315
  first.unmount();
311
316
 
312
- render(<ChromeHarness store={store} chrome={sectionChrome} />);
317
+ await renderSettled(<ChromeHarness store={store} chrome={sectionChrome} />);
313
318
  expect(screen.getByRole('button', { name: 'Notes' })).toHaveAttribute('aria-expanded', 'false');
314
319
  expect(screen.queryByTestId('notes-body')).toBeNull();
315
320
  });
316
321
 
317
- it('keeps a folded settings group across a remount', () => {
322
+ it('keeps a folded settings group across a remount', async () => {
318
323
  const sectioned: Instrument = {
319
324
  ...stubInstrument,
320
325
  config: f.schema({ size: f.number(1).section('Shape') }),
321
326
  };
322
327
  const store = makeChromeStore();
323
- const first = render(<ChromeHarness store={store} instrument={sectioned} />);
328
+ const first = await renderSettled(<ChromeHarness store={store} instrument={sectioned} />);
324
329
  const twisty = screen.getByRole('button', { name: 'Shape' });
325
330
  expect(twisty).toHaveAttribute('aria-expanded', 'true');
326
331
  fireEvent.click(twisty);
327
332
  first.unmount();
328
333
 
329
- render(<ChromeHarness store={store} instrument={sectioned} />);
334
+ await renderSettled(<ChromeHarness store={store} instrument={sectioned} />);
330
335
  expect(screen.getByRole('button', { name: 'Shape' })).toHaveAttribute('aria-expanded', 'false');
331
336
  });
332
337
 
333
- it('reopens a section that starts collapsed, and keeps it open across a remount', () => {
338
+ it('reopens a section that starts collapsed, and keeps it open across a remount', async () => {
334
339
  const collapsedChrome: ChromeProps['chrome'] = [
335
340
  {
336
341
  id: 'notes',
@@ -343,12 +348,12 @@ describe('<TrialChrome> — state that outlives a mount', () => {
343
348
  },
344
349
  ];
345
350
  const store = makeChromeStore();
346
- const first = render(<ChromeHarness store={store} chrome={collapsedChrome} />);
351
+ const first = await renderSettled(<ChromeHarness store={store} chrome={collapsedChrome} />);
347
352
  expect(screen.queryByTestId('notes-body')).toBeNull();
348
353
  fireEvent.click(screen.getByRole('button', { name: 'Notes' }));
349
354
  first.unmount();
350
355
 
351
- render(<ChromeHarness store={store} chrome={collapsedChrome} />);
356
+ await renderSettled(<ChromeHarness store={store} chrome={collapsedChrome} />);
352
357
  expect(screen.getByTestId('notes-body')).toBeInTheDocument();
353
358
  });
354
359
  });
@@ -4,7 +4,8 @@
4
4
  * `SingletonExperiment` — so `useTrialState()` threw everywhere else, which
5
5
  * made the documented hook pattern unreachable from an instrument's `render`.
6
6
  */
7
- import { act, render, screen } from '@testing-library/react';
7
+ import { act, screen } from '@testing-library/react';
8
+ import { renderSettled } from '@weasel-js/react/testing/renderSettled';
8
9
  import { beforeAll, describe, expect, it, vi } from 'vitest';
9
10
  import { defineInstrument } from '../instrument/defineInstrument';
10
11
  import { Lab } from '../lab/Lab';
@@ -30,16 +31,16 @@ const reader = defineInstrument<{ hits: number }, Record<string, never>>({
30
31
  });
31
32
 
32
33
  describe('<Trial> trial-scoped hooks', () => {
33
- it('lets an instrument read its own trial state through useTrialState', () => {
34
- render(<Lab instruments={[reader]} defaultInstrument="Reader" />);
34
+ it('lets an instrument read its own trial state through useTrialState', async () => {
35
+ await renderSettled(<Lab instruments={[reader]} defaultInstrument="Reader" />);
35
36
  expect(screen.getByTestId('probe')).toHaveTextContent('hits:7');
36
37
  });
37
38
  });
38
39
 
39
40
  describe('<Trial> root element', () => {
40
- it('carries its trial id, so a lab can find a trial from outside', () => {
41
+ it('carries its trial id, so a lab can find a trial from outside', async () => {
41
42
  let labRef: LabContextValue | null = null;
42
- const { container } = render(
43
+ const { container } = await renderSettled(
43
44
  <Lab instruments={[reader]} defaultInstrument="Reader">
44
45
  <LabContext.Consumer>
45
46
  {(value) => {
@@ -49,7 +50,7 @@ describe('<Trial> root element', () => {
49
50
  </LabContext.Consumer>
50
51
  </Lab>,
51
52
  );
52
- act(() => (labRef as LabContextValue | null)?.addTrial('Reader'));
53
+ await act(async () => (labRef as LabContextValue | null)?.addTrial('Reader'));
53
54
  const ids = (labRef as LabContextValue | null)?.trials.map((t) => t.id) ?? [];
54
55
  expect(ids).toHaveLength(2);
55
56
  const roots = container.querySelectorAll<HTMLElement>('.lk-trial[data-trial-id]');