Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
"pnpm": {
"overrides": {
"jacaranda": "workspace:*"
}
},
"ignoredBuiltDependencies": [
"@swc/core",
"esbuild"
]
}
}
143 changes: 143 additions & 0 deletions packages/jacaranda/__mocks__/@testing-library/react-native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Manual mock for @testing-library/react-native
// Provides basic render function for testing styled components
import React from 'react';
import type { ReactElement, ComponentType } from 'react';

interface RenderResult {
toJSON: () => unknown;
getByTestId: (testId: string) => { props: Record<string, unknown> };
queryByTestId: (testId: string) => { props: Record<string, unknown> } | null;
unmount: () => void;
rerender: (ui: ReactElement) => void;
}

// Recursively render component tree to get final props
const renderToProps = (element: ReactElement): ReactElement | null => {
if (!element || typeof element !== 'object') return null;

const { type, props } = element;

// If it's a function component, call it to get the rendered output
if (typeof type === 'function') {
try {
// Check if it's a class component
if (type.prototype && type.prototype.isReactComponent) {
const instance = new (type as any)(props);
const rendered = instance.render();
return renderToProps(rendered);
}
// Function component
const rendered = (type as any)(props);
return renderToProps(rendered);
} catch {
// If rendering fails, return the element as-is
return element;
}
}

// If it's a memo component, unwrap it
if (type && typeof type === 'object' && '$$typeof' in type) {
const memoType = type as { $$typeof: symbol; type?: ComponentType };
if (memoType.type && typeof memoType.type === 'function') {
try {
const rendered = (memoType.type as any)(props);
return renderToProps(rendered);
} catch {
return element;
}
}
}

// For host components (strings like 'View', 'Text'), return the element
if (typeof type === 'string') {
return element;
}

// For forwardRef components
if (type && typeof type === 'object' && 'render' in type) {
try {
const rendered = (type as any).render(props, null);
return renderToProps(rendered);
} catch {
return element;
}
}

return element;
};

const findByTestId = (element: ReactElement | null, testId: string): ReactElement | null => {
if (!element) return null;

// First, try to render the element to get its actual output
const rendered = renderToProps(element);
if (!rendered) return null;

// Check if this element has the testID
if (rendered.props?.testID === testId) {
return rendered;
}

// Search in children
const children = rendered.props?.children;
if (children) {
if (Array.isArray(children)) {
for (const child of children) {
if (React.isValidElement(child)) {
const found = findByTestId(child as ReactElement, testId);
if (found) return found;
}
}
} else if (React.isValidElement(children)) {
return findByTestId(children as ReactElement, testId);
}
}

return null;
};

export const render = (ui: ReactElement): RenderResult => {
let currentUi = ui;

return {
toJSON: () => {
const rendered = renderToProps(currentUi);
return rendered;
},
getByTestId: (testId: string) => {
const found = findByTestId(currentUi, testId);
if (!found) {
throw new Error(`Unable to find element with testID: ${testId}`);
}
return { props: { ...found.props } };
},
queryByTestId: (testId: string) => {
const found = findByTestId(currentUi, testId);
return found ? { props: { ...found.props } } : null;
},
unmount: () => {},
rerender: (newUi: ReactElement) => {
currentUi = newUi;
},
};
};

export const screen = {
getByTestId: () => {
throw new Error('screen.getByTestId requires render() to be called first');
},
};

export const fireEvent = {
press: () => {},
changeText: () => {},
scroll: () => {},
};

export const waitFor = async <T>(callback: () => T | Promise<T>): Promise<T> => {
return callback();
};

export const act = async (callback: () => void | Promise<void>): Promise<void> => {
await callback();
};
55 changes: 55 additions & 0 deletions packages/jacaranda/__mocks__/react-native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Manual mock for react-native
// This allows testing without the actual react-native package
import React from 'react';

// Mock StyleSheet
export const StyleSheet = {
create: <T extends Record<string, object>>(styles: T): T => styles,
flatten: (style: unknown) => style,
compose: (style1: unknown, style2: unknown) => [style1, style2],
absoluteFill: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 },
absoluteFillObject: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 },
hairlineWidth: 1,
};

// Create basic component mocks
const createMockComponent = (name: string) => {
const Component = React.forwardRef((props: any, ref: any) => {
return React.createElement(name, { ...props, ref }, props.children);
});
Component.displayName = name;
return Component;
};

export const View = createMockComponent('View');
export const Text = createMockComponent('Text');
export const Image = createMockComponent('Image');
export const TouchableOpacity = createMockComponent('TouchableOpacity');
export const TouchableHighlight = createMockComponent('TouchableHighlight');
export const TouchableWithoutFeedback = createMockComponent('TouchableWithoutFeedback');
export const ScrollView = createMockComponent('ScrollView');
export const TextInput = createMockComponent('TextInput');

export const Platform = {
OS: 'ios' as const,
select: <T>(obj: { ios?: T; android?: T; default?: T }): T | undefined => obj.ios ?? obj.default,
};

// Type exports
export type ViewStyle = Record<string, unknown>;
export type TextStyle = Record<string, unknown>;
export type ImageStyle = Record<string, unknown>;
export type StyleProp<T> = T | T[] | null | undefined;

export default {
StyleSheet,
View,
Text,
Image,
TouchableOpacity,
TouchableHighlight,
TouchableWithoutFeedback,
ScrollView,
TextInput,
Platform,
};
2 changes: 1 addition & 1 deletion packages/jacaranda/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jacaranda",
"description": "A lightweight styling library for React Native",
"version": "0.1.0",
"version": "0.1.1-beta.2",
"license": "ISC",
"type": "module",
"author": {
Expand Down
Loading