1import { describe, it, expect } from "vitest";
2import React from "react";
4// Serialize a React element tree to a plain object for snapshot comparison.
5// Our mocks return string tags (e.g. "MacOSCheckbox") which react-test-renderer
6// can't render, so we walk the element tree directly.
7function toTree(element) {
8 if (element == null) return null;
9 if (typeof element === "string") return element;
10 if (typeof element === "number") return String(element);
11 if (Array.isArray(element)) return element.map(toTree);
12 const { type, props } = element;
13 const { children, ...rest } = props || {};
15 typeof type === "string"
17 : type?.displayName || type?.name || "Unknown";
18 const node = { type: name };
19 // Strip functions from props (event handlers) for stable snapshots
20 const cleanProps = {};
22 for (const [k, v] of Object.entries(rest)) {
23 if (typeof v !== "function") {
28 if (hasProps) node.props = cleanProps;
29 if (children != null) {
30 const kids = Array.isArray(children)
31 ? children.map(toTree)
33 const filtered = kids.filter((k) => k != null);
34 if (filtered.length > 0) node.children = filtered;
39const render = (element) => ({
40 toJSON: () => toTree(element),
44 createNativeElementNoKidsImpl as el,
45 createNativeElementImpl as elK,
46} from "../src/Yoga/React/Native/Internal.js";
48// Leaf components (no children)
49import { buttonImpl } from "../src/Yoga/React/Native/MacOS/Button.js";
50import { checkboxImpl } from "../src/Yoga/React/Native/MacOS/Checkbox.js";
51import { radioButtonImpl } from "../src/Yoga/React/Native/MacOS/RadioButton.js";
52import { searchFieldImpl } from "../src/Yoga/React/Native/MacOS/SearchField.js";
53import { tokenFieldImpl } from "../src/Yoga/React/Native/MacOS/TokenField.js";
54import { separatorImpl } from "../src/Yoga/React/Native/MacOS/Separator.js";
55import { helpButtonImpl } from "../src/Yoga/React/Native/MacOS/HelpButton.js";
56import { pathControlImpl } from "../src/Yoga/React/Native/MacOS/PathControl.js";
57import { textFieldImpl } from "../src/Yoga/React/Native/MacOS/TextField.js";
58import { sliderImpl } from "../src/Yoga/React/Native/MacOS/Slider.js";
59import { switchImpl } from "../src/Yoga/React/Native/MacOS/Switch.js";
60import { progressImpl } from "../src/Yoga/React/Native/MacOS/Progress.js";
61import { datePickerImpl } from "../src/Yoga/React/Native/MacOS/DatePicker.js";
62import { colorWellImpl } from "../src/Yoga/React/Native/MacOS/ColorWell.js";
63import { popUpImpl } from "../src/Yoga/React/Native/MacOS/PopUp.js";
64import { textEditorImpl } from "../src/Yoga/React/Native/MacOS/TextEditor.js";
65import { levelIndicatorImpl } from "../src/Yoga/React/Native/MacOS/LevelIndicator.js";
66import { segmentedImpl } from "../src/Yoga/React/Native/MacOS/Segmented.js";
67import { stepperImpl } from "../src/Yoga/React/Native/MacOS/Stepper.js";
68import { comboBoxImpl } from "../src/Yoga/React/Native/MacOS/ComboBox.js";
69import { imageImpl } from "../src/Yoga/React/Native/MacOS/Image.js";
70import { animatedImageImpl } from "../src/Yoga/React/Native/MacOS/AnimatedImage.js";
71import { videoPlayerImpl } from "../src/Yoga/React/Native/MacOS/VideoPlayer.js";
72import { tableViewImpl } from "../src/Yoga/React/Native/MacOS/TableView.js";
73import { outlineViewImpl } from "../src/Yoga/React/Native/MacOS/OutlineView.js";
74import { mapViewImpl } from "../src/Yoga/React/Native/MacOS/MapView.js";
75import { pdfViewImpl } from "../src/Yoga/React/Native/MacOS/PDFView.js";
76import { cameraViewImpl } from "../src/Yoga/React/Native/MacOS/CameraView.js";
78// Container components (with children)
79import { sheetImpl } from "../src/Yoga/React/Native/MacOS/Sheet.js";
80import { boxImpl } from "../src/Yoga/React/Native/MacOS/Box.js";
81import { popoverImpl } from "../src/Yoga/React/Native/MacOS/Popover.js";
82import { nativeScrollViewImpl } from "../src/Yoga/React/Native/MacOS/ScrollView.js";
83import { visualEffectImpl } from "../src/Yoga/React/Native/MacOS/VisualEffect.js";
84import { contextMenuImpl } from "../src/Yoga/React/Native/MacOS/ContextMenu.js";
85import { splitViewImpl } from "../src/Yoga/React/Native/MacOS/SplitView.js";
86import { tabViewImpl } from "../src/Yoga/React/Native/MacOS/TabView.js";
87import { patternBackgroundImpl } from "../src/Yoga/React/Native/MacOS/PatternBackground.js";
91describe("macOS component snapshots", () => {
95 const { toJSON } = render(
98 bezelStyle: "rounded",
102 expect(toJSON()).toMatchSnapshot();
105 it("Checkbox checked", () => {
106 const { toJSON } = render(
109 title: "Accept terms",
113 expect(toJSON()).toMatchSnapshot();
116 it("Checkbox unchecked", () => {
117 const { toJSON } = render(
120 title: "Accept terms",
124 expect(toJSON()).toMatchSnapshot();
127 it("RadioButton selected", () => {
128 const { toJSON } = render(
129 el(radioButtonImpl, {
135 expect(toJSON()).toMatchSnapshot();
138 it("RadioButton unselected", () => {
139 const { toJSON } = render(
140 el(radioButtonImpl, {
146 expect(toJSON()).toMatchSnapshot();
149 it("Switch on", () => {
150 const { toJSON } = render(el(switchImpl, { value: true, onChange: noop }));
151 expect(toJSON()).toMatchSnapshot();
155 const { toJSON } = render(
163 expect(toJSON()).toMatchSnapshot();
166 it("Stepper", () => {
167 const { toJSON } = render(
175 expect(toJSON()).toMatchSnapshot();
178 it("Segmented", () => {
179 const { toJSON } = render(
181 values: ["A", "B", "C"],
186 expect(toJSON()).toMatchSnapshot();
189 it("HelpButton", () => {
190 const { toJSON } = render(el(helpButtonImpl, { onPress: noop }));
191 expect(toJSON()).toMatchSnapshot();
195 const { toJSON } = render(
197 items: ["One", "Two"],
202 expect(toJSON()).toMatchSnapshot();
205 it("ComboBox", () => {
206 const { toJSON } = render(
208 items: ["foo", "bar"],
213 expect(toJSON()).toMatchSnapshot();
218 it("TextField", () => {
219 const { toJSON } = render(
222 placeholder: "Type here",
226 expect(toJSON()).toMatchSnapshot();
229 it("SearchField", () => {
230 const { toJSON } = render(
231 el(searchFieldImpl, {
233 placeholder: "Search...",
237 expect(toJSON()).toMatchSnapshot();
240 it("TokenField", () => {
241 const { toJSON } = render(
243 tokens: ["swift", "purescript"],
244 placeholder: "Add tag",
247 expect(toJSON()).toMatchSnapshot();
250 it("TextEditor", () => {
251 const { toJSON } = render(
252 el(textEditorImpl, { text: "Some long text", onChangeText: noop }),
254 expect(toJSON()).toMatchSnapshot();
259 it("Progress determinate", () => {
260 const { toJSON } = render(el(progressImpl, { value: 0.6, style: "bar" }));
261 expect(toJSON()).toMatchSnapshot();
264 it("LevelIndicator", () => {
265 const { toJSON } = render(
266 el(levelIndicatorImpl, { value: 3, minValue: 0, maxValue: 5 }),
268 expect(toJSON()).toMatchSnapshot();
273 it("DatePicker", () => {
274 const { toJSON } = render(
275 el(datePickerImpl, { date: "2025-01-15", onChange: noop }),
277 expect(toJSON()).toMatchSnapshot();
280 it("ColorWell", () => {
281 const { toJSON } = render(
282 el(colorWellImpl, { color: "#ff0000", onChange: noop }),
284 expect(toJSON()).toMatchSnapshot();
287 it("PathControl", () => {
288 const { toJSON } = render(
289 el(pathControlImpl, {
290 url: "/Users/test/file.txt",
291 pathStyle: "standard",
294 expect(toJSON()).toMatchSnapshot();
300 const { toJSON } = render(
301 el(imageImpl, { source: "test.png", resizeMode: "cover" }),
303 expect(toJSON()).toMatchSnapshot();
306 it("AnimatedImage", () => {
307 const { toJSON } = render(el(animatedImageImpl, { source: "anim.gif" }));
308 expect(toJSON()).toMatchSnapshot();
311 it("VideoPlayer", () => {
312 const { toJSON } = render(
313 el(videoPlayerImpl, { source: "video.mp4", autoPlay: false }),
315 expect(toJSON()).toMatchSnapshot();
320 it("Separator horizontal", () => {
321 const { toJSON } = render(el(separatorImpl, { vertical: false }));
322 expect(toJSON()).toMatchSnapshot();
325 it("Separator vertical", () => {
326 const { toJSON } = render(el(separatorImpl, { vertical: true }));
327 expect(toJSON()).toMatchSnapshot();
332 it("TableView", () => {
333 const { toJSON } = render(
336 { title: "Name", width: 100 },
337 { title: "Age", width: 50 },
344 alternatingRows: true,
347 expect(toJSON()).toMatchSnapshot();
350 it("OutlineView", () => {
351 const { toJSON } = render(
352 el(outlineViewImpl, {
358 { id: "1.1", title: "Child A", sfSymbol: "doc" },
359 { id: "1.2", title: "Child B" },
363 headerVisible: false,
367 expect(toJSON()).toMatchSnapshot();
370 it("MapView", () => {
371 const { toJSON } = render(
374 longitude: -122.4194,
376 longitudeDelta: 0.05,
378 showsUserLocation: false,
379 annotations: [{ latitude: 37.7749, longitude: -122.4194, title: "SF" }],
380 onRegionChange: noop,
381 onSelectAnnotation: noop,
384 expect(toJSON()).toMatchSnapshot();
387 it("PDFView", () => {
388 const { toJSON } = render(
390 source: "/tmp/test.pdf",
392 displayMode: "singlePageContinuous",
396 expect(toJSON()).toMatchSnapshot();
401 it("Sheet with child", () => {
402 const { toJSON } = render(
403 elK(sheetImpl, { visible: true, onDismiss: noop }, [
404 el("View", { key: "1" }),
407 expect(toJSON()).toMatchSnapshot();
410 it("Box with child", () => {
411 const { toJSON } = render(
412 elK(boxImpl, { title: "Group" }, [el("View", { key: "1" })]),
414 expect(toJSON()).toMatchSnapshot();
417 it("Popover with child", () => {
418 const { toJSON } = render(
419 elK(popoverImpl, { visible: true }, [el("View", { key: "1" })]),
421 expect(toJSON()).toMatchSnapshot();
424 it("ScrollView with child", () => {
425 const { toJSON } = render(
426 elK(nativeScrollViewImpl, {}, [el("View", { key: "1" })]),
428 expect(toJSON()).toMatchSnapshot();
431 it("VisualEffect with child", () => {
432 const { toJSON } = render(
433 elK(visualEffectImpl, { material: "sidebar" }, [
434 el("View", { key: "1" }),
437 expect(toJSON()).toMatchSnapshot();
440 it("ContextMenu with child", () => {
441 const { toJSON } = render(
442 elK(contextMenuImpl, { menuItems: [{ title: "Copy" }] }, [
443 el("View", { key: "1" }),
446 expect(toJSON()).toMatchSnapshot();
449 it("SplitView with children", () => {
450 const { toJSON } = render(
451 elK(splitViewImpl, { vertical: false }, [
452 el("View", { key: "1" }),
453 el("View", { key: "2" }),
456 expect(toJSON()).toMatchSnapshot();
459 it("TabView with children", () => {
460 const { toJSON } = render(
461 elK(tabViewImpl, { selectedIndex: 0 }, [el("View", { key: "1" })]),
463 expect(toJSON()).toMatchSnapshot();
466 it("PatternBackground with child", () => {
467 const { toJSON } = render(
468 elK(patternBackgroundImpl, { pattern: "dots" }, [
469 el("View", { key: "1" }),
472 expect(toJSON()).toMatchSnapshot();
475 it("CameraView", () => {
476 const { toJSON } = render(el(cameraViewImpl, { active: true }));
477 expect(toJSON()).toMatchSnapshot();
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list