one
Version:
One is a new React Framework that makes Vite serve both native and web.
464 lines • 14.8 kB
JavaScript
var import_vitest = require("vitest");
var import_react = require("react");
var import_server = require("react-dom/server");
var import_WebStackView = require("../WebStackView.cjs");
var import_ScreenRenderContext = require("../ScreenRenderContext.cjs");
function makeRoute(name) {
return {
key: `${name}-key`,
name,
params: void 0
};
}
function makeState(names, index) {
return {
key: "stack-1",
index,
routeNames: names,
routes: names.map(makeRoute),
type: "stack",
stale: false,
preloadedRoutes: []
};
}
function makeDescriptors(perRoute) {
const out = {};
for (const name of Object.keys(perRoute)) {
out[`${name}-key`] = {
options: perRoute[name].options,
render: () => perRoute[name].content ?? null,
navigation: {}
};
}
return out;
}
(0, import_vitest.describe)("resolveOverlayRender", () => {
const A = () => null;
const B = () => null;
(0, import_vitest.it)("prefers per-route render over context", () => {
const result = (0, import_WebStackView.resolveOverlayRender)({
presentation: "formSheet",
render: {
web: A
}
}, {
web: B
});
(0, import_vitest.expect)(result).toBe(A);
});
(0, import_vitest.it)("falls back to context render when route has none", () => {
const result = (0, import_WebStackView.resolveOverlayRender)({
presentation: "formSheet"
}, {
web: B
});
(0, import_vitest.expect)(result).toBe(B);
});
(0, import_vitest.it)("returns undefined when nothing is configured", () => {
(0, import_vitest.expect)((0, import_WebStackView.resolveOverlayRender)({
presentation: "formSheet"
}, void 0)).toBeUndefined();
(0, import_vitest.expect)((0, import_WebStackView.resolveOverlayRender)({
presentation: "formSheet"
}, {
ios: A
})).toBeUndefined();
});
(0, import_vitest.it)("does not pick from ios/android slots on web", () => {
(0, import_vitest.expect)((0, import_WebStackView.resolveOverlayRender)({
presentation: "formSheet",
render: {
ios: A
}
}, {
web: B
})).toBe(B);
});
});
(0, import_vitest.describe)("OverlayHost", () => {
(0, import_vitest.it)("invokes the render component with route props for overlay presentations", () => {
const captured = [];
const Render = import_vitest.vi.fn(props => {
captured.push(props);
return (0, import_react.createElement)("div", {
"data-modal": props.routeKey
}, props.children);
});
const descriptors = makeDescriptors({
filter: {
options: {
presentation: "formSheet",
sheetAllowedDetents: [0.5, 1],
sheetGrabberVisible: true
},
content: (0, import_react.createElement)("span", null, "filter-body")
}
});
const markup = (0, import_server.renderToStaticMarkup)((0, import_react.createElement)(import_WebStackView.OverlayHost, {
route: makeRoute("filter"),
descriptor: descriptors["filter-key"],
contextRender: {
web: Render
},
onDismiss: () => {}
}));
(0, import_vitest.expect)(Render).toHaveBeenCalledTimes(1);
(0, import_vitest.expect)(captured[0]).toMatchObject({
routeKey: "filter-key",
presentation: "formSheet",
sheetAllowedDetents: [0.5, 1],
sheetGrabberVisible: true,
dismissible: true
});
(0, import_vitest.expect)(typeof captured[0].dismiss).toBe("function");
(0, import_vitest.expect)(markup).toContain('data-modal="filter-key"');
(0, import_vitest.expect)(markup).toContain("filter-body");
});
(0, import_vitest.it)("falls back to inline rendering when no render is configured", () => {
const descriptors = makeDescriptors({
filter: {
options: {
presentation: "formSheet"
},
content: (0, import_react.createElement)("span", null, "inline-content")
}
});
const markup = (0, import_server.renderToStaticMarkup)((0, import_react.createElement)(import_WebStackView.OverlayHost, {
route: makeRoute("filter"),
descriptor: descriptors["filter-key"],
contextRender: void 0,
onDismiss: () => {}
}));
(0, import_vitest.expect)(markup).toContain("inline-content");
});
(0, import_vitest.it)("skips render when presentation is not an overlay", () => {
const Render = import_vitest.vi.fn(() => null);
const descriptors = makeDescriptors({
home: {
options: {
presentation: "card"
},
content: (0, import_react.createElement)("span", null, "card-content")
}
});
const markup = (0, import_server.renderToStaticMarkup)((0, import_react.createElement)(import_WebStackView.OverlayHost, {
route: makeRoute("home"),
descriptor: descriptors["home-key"],
contextRender: {
web: Render
},
onDismiss: () => {}
}));
(0, import_vitest.expect)(Render).not.toHaveBeenCalled();
(0, import_vitest.expect)(markup).toContain("card-content");
});
(0, import_vitest.it)("per-route render overrides context render", () => {
const ContextRender = import_vitest.vi.fn(() => null);
const PerRoute = import_vitest.vi.fn(() => (0, import_react.createElement)("em", null, "per-route"));
const descriptors = makeDescriptors({
sheet: {
options: {
presentation: "formSheet",
render: {
web: PerRoute
}
},
content: (0, import_react.createElement)("span", null, "body")
}
});
const markup = (0, import_server.renderToStaticMarkup)((0, import_react.createElement)(import_WebStackView.OverlayHost, {
route: makeRoute("sheet"),
descriptor: descriptors["sheet-key"],
contextRender: {
web: ContextRender
},
onDismiss: () => {}
}));
(0, import_vitest.expect)(PerRoute).toHaveBeenCalledTimes(1);
(0, import_vitest.expect)(ContextRender).not.toHaveBeenCalled();
(0, import_vitest.expect)(markup).toContain("per-route");
});
(0, import_vitest.it)("dismiss callback wraps the supplied onDismiss", () => {
const onDismiss = import_vitest.vi.fn();
let capturedDismiss;
const Render = props => {
capturedDismiss = props.dismiss;
return null;
};
const descriptors = makeDescriptors({
sheet: {
options: {
presentation: "formSheet"
}
}
});
(0, import_server.renderToStaticMarkup)((0, import_react.createElement)(import_WebStackView.OverlayHost, {
route: makeRoute("sheet"),
descriptor: descriptors["sheet-key"],
contextRender: {
web: Render
},
onDismiss
}));
(0, import_vitest.expect)(typeof capturedDismiss).toBe("function");
capturedDismiss();
(0, import_vitest.expect)(onDismiss).toHaveBeenCalledTimes(1);
});
(0, import_vitest.it)("respects gestureEnabled: false as dismissible: false", () => {
let captured;
const Render = props => {
captured = props;
return null;
};
const descriptors = makeDescriptors({
sheet: {
options: {
presentation: "formSheet",
gestureEnabled: false
}
}
});
(0, import_server.renderToStaticMarkup)((0, import_react.createElement)(import_WebStackView.OverlayHost, {
route: makeRoute("sheet"),
descriptor: descriptors["sheet-key"],
contextRender: {
web: Render
},
onDismiss: () => {}
}));
(0, import_vitest.expect)(captured.dismissible).toBe(false);
});
});
(0, import_vitest.describe)("WebStackView overlay dispatch", () => {
(0, import_vitest.it)("renders each overlay route via the configured render", () => {
const calls = [];
const Render = props => {
calls.push(props.routeKey);
return (0, import_react.createElement)("div", {
"data-route": props.routeKey
});
};
const state = makeState(["home", "filter", "help"], 2);
const descriptors = makeDescriptors({
home: {
options: {
presentation: "card"
}
},
filter: {
options: {
presentation: "formSheet"
}
},
help: {
options: {
presentation: "modal"
}
}
});
const navigation = {
dispatch: import_vitest.vi.fn()
};
const markup = (0, import_server.renderToStaticMarkup)((0, import_react.createElement)(import_ScreenRenderContext.StackRenderProvider, {
value: {
web: Render
}
}, (0, import_react.createElement)(import_WebStackView.WebStackView, {
state,
navigation,
descriptors
})));
(0, import_vitest.expect)(calls).toEqual(["filter-key", "help-key"]);
(0, import_vitest.expect)(markup).toContain('data-route="filter-key"');
(0, import_vitest.expect)(markup).toContain('data-route="help-key"');
});
(0, import_vitest.it)("leaves overlay routes in the underlying NativeStackView when no render is configured", () => {
const state = makeState(["home", "filter"], 1);
const descriptors = makeDescriptors({
home: {
options: {
presentation: "card"
}
},
filter: {
options: {
presentation: "formSheet"
},
content: (0, import_react.createElement)("span", null, "should-not-render-as-overlay")
}
});
const navigation = {
dispatch: import_vitest.vi.fn()
};
const markup = (0, import_server.renderToStaticMarkup)((0, import_react.createElement)(import_WebStackView.WebStackView, {
state,
navigation,
descriptors
}));
(0, import_vitest.expect)(markup).not.toContain("should-not-render-as-overlay");
});
(0, import_vitest.it)("passes open: true to the regular overlay render", () => {
let captured;
const Render = props => {
captured = props;
return null;
};
const state = makeState(["home", "sheet"], 1);
const descriptors = makeDescriptors({
home: {
options: {
presentation: "card"
}
},
sheet: {
options: {
presentation: "formSheet"
}
}
});
const navigation = {
dispatch: import_vitest.vi.fn()
};
(0, import_server.renderToStaticMarkup)((0, import_react.createElement)(import_ScreenRenderContext.StackRenderProvider, {
value: {
web: Render
}
}, (0, import_react.createElement)(import_WebStackView.WebStackView, {
state,
navigation,
descriptors
})));
(0, import_vitest.expect)(captured.open).toBe(true);
(0, import_vitest.expect)(captured.routeName).toBe("sheet");
});
(0, import_vitest.it)("keepMounted: keeps rendering the route via the persistent slot after the route is popped", () => {
const calls = [];
let mountTrackerCalls = 0;
const Render = props => {
calls.push({
name: props.routeName,
open: props.open
});
return (0, import_react.createElement)("div", {
"data-route": props.routeName,
"data-open": String(props.open)
}, props.children);
};
const MountTracker = () => {
mountTrackerCalls++;
return (0, import_react.createElement)("span", null, `mounted-${mountTrackerCalls}`);
};
const initialState = makeState(["home", "settings"], 1);
const settingsDescriptor = {
options: {
presentation: "formSheet",
keepMounted: true
},
render: () => (0, import_react.createElement)(MountTracker),
navigation: {}
};
const descriptors = {
"home-key": {
options: {
presentation: "card"
},
render: () => null,
navigation: {}
},
"settings-key": settingsDescriptor
};
const navigation = {
dispatch: import_vitest.vi.fn()
};
const tree = (0, import_react.createElement)(import_ScreenRenderContext.StackRenderProvider, {
value: {
web: Render
}
}, (0, import_react.createElement)(import_WebStackView.WebStackView, {
state: initialState,
navigation,
descriptors
}));
const out1 = (0, import_server.renderToStaticMarkup)(tree);
(0, import_vitest.expect)(calls.some(c => c.name === "settings" && c.open === true)).toBe(true);
(0, import_vitest.expect)(out1).toContain('data-route="settings"');
(0, import_vitest.expect)(out1).toContain('data-open="true"');
(0, import_vitest.expect)(calls).toHaveLength(1);
});
(0, import_vitest.it)("peels off only overlay routes with a render configured, leaving render-less overlays in the underlying view", () => {
const PerRoute = import_vitest.vi.fn(() => (0, import_react.createElement)("div", {
"data-route": "filter"
}));
const state = makeState(["home", "help", "filter"], 2);
const descriptors = makeDescriptors({
home: {
options: {
presentation: "card"
}
},
help: {
options: {
presentation: "modal"
}
},
// overlay-presented, no render
filter: {
options: {
presentation: "formSheet",
render: {
web: PerRoute
}
}
}
});
const navigation = {
dispatch: import_vitest.vi.fn()
};
(0, import_server.renderToStaticMarkup)((0, import_react.createElement)(import_WebStackView.WebStackView, {
state,
navigation,
descriptors
}));
(0, import_vitest.expect)(PerRoute).toHaveBeenCalledTimes(1);
});
(0, import_vitest.it)("dispatches a pop action when an overlay calls dismiss", () => {
let captured;
const Render = props => {
captured = props;
return null;
};
const state = makeState(["home", "filter"], 1);
const descriptors = makeDescriptors({
home: {
options: {
presentation: "card"
}
},
filter: {
options: {
presentation: "formSheet"
}
}
});
const dispatch = import_vitest.vi.fn();
const navigation = {
dispatch,
isFocused: () => true
};
(0, import_server.renderToStaticMarkup)((0, import_react.createElement)(import_ScreenRenderContext.StackRenderProvider, {
value: {
web: Render
}
}, (0, import_react.createElement)(import_WebStackView.WebStackView, {
state,
navigation,
descriptors
})));
captured.dismiss();
(0, import_vitest.expect)(dispatch).toHaveBeenCalledTimes(1);
const action = dispatch.mock.calls[0][0];
(0, import_vitest.expect)(action.type).toBe("POP");
(0, import_vitest.expect)(action.source).toBe("filter-key");
(0, import_vitest.expect)(action.target).toBe("stack-1");
});
});