Skip to content
Merged
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
456 changes: 341 additions & 115 deletions ROADMAP.md

Large diffs are not rendered by default.

65 changes: 65 additions & 0 deletions __tests__/components/FloatingActionButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from "react";
import { render, fireEvent } from "@testing-library/react-native";

import { FloatingActionButton } from "~/components/common/FloatingActionButton";
import type { FABAction } from "~/components/common/FloatingActionButton";

describe("FloatingActionButton", () => {
it("renders with default props", () => {
const { getByTestId } = render(<FloatingActionButton />);
expect(getByTestId("fab")).toBeTruthy();
expect(getByTestId("fab-button")).toBeTruthy();
});

it("calls onPress in simple mode", () => {
const onPress = jest.fn();
const { getByTestId } = render(<FloatingActionButton onPress={onPress} />);
fireEvent.press(getByTestId("fab-button"));
expect(onPress).toHaveBeenCalledTimes(1);
});

it("expands to show actions when pressed with actions", () => {
const actions: FABAction[] = [
{ id: "a1", label: "Action 1", onPress: jest.fn() },
{ id: "a2", label: "Action 2", onPress: jest.fn() },
];
const { getByTestId, queryByTestId } = render(
<FloatingActionButton actions={actions} />
);

// Actions not visible initially
expect(queryByTestId("fab-action-a1")).toBeNull();

// Press to expand
fireEvent.press(getByTestId("fab-button"));
expect(getByTestId("fab-action-a1")).toBeTruthy();
expect(getByTestId("fab-action-a2")).toBeTruthy();
});

it("calls action onPress and collapses", () => {
const actionFn = jest.fn();
const actions: FABAction[] = [
{ id: "a1", label: "Action 1", onPress: actionFn },
];
const { getByTestId, queryByTestId } = render(
<FloatingActionButton actions={actions} />
);

fireEvent.press(getByTestId("fab-button"));
fireEvent.press(getByTestId("fab-action-a1"));

expect(actionFn).toHaveBeenCalledTimes(1);
// Should collapse after action press
expect(queryByTestId("fab-action-a1")).toBeNull();
});

it("has correct accessibility labels", () => {
const { getByTestId } = render(<FloatingActionButton />);
expect(getByTestId("fab-button").props.accessibilityRole).toBe("button");
});

it("uses custom testID", () => {
const { getByTestId } = render(<FloatingActionButton testID="my-fab" />);
expect(getByTestId("my-fab")).toBeTruthy();
});
});
31 changes: 31 additions & 0 deletions __tests__/components/SkeletonDashboard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from "react";
import { render } from "@testing-library/react-native";

import { SkeletonDashboard } from "~/components/common/SkeletonDashboard";

describe("SkeletonDashboard", () => {
it("renders with default props", () => {
const { getByTestId } = render(<SkeletonDashboard />);
expect(getByTestId("skeleton-dashboard")).toBeTruthy();
});

it("renders the correct number of cards", () => {
const { getByTestId, queryByTestId } = render(<SkeletonDashboard cards={3} />);
expect(getByTestId("skeleton-dashboard-card-0")).toBeTruthy();
expect(getByTestId("skeleton-dashboard-card-1")).toBeTruthy();
expect(getByTestId("skeleton-dashboard-card-2")).toBeTruthy();
expect(queryByTestId("skeleton-dashboard-card-3")).toBeNull();
});

it("has correct accessibility attributes", () => {
const { getByTestId } = render(<SkeletonDashboard />);
const root = getByTestId("skeleton-dashboard");
expect(root.props.accessibilityLabel).toBe("Loading dashboard");
expect(root.props.accessibilityRole).toBe("progressbar");
});

it("uses custom testID", () => {
const { getByTestId } = render(<SkeletonDashboard testID="my-dash" />);
expect(getByTestId("my-dash")).toBeTruthy();
});
});
37 changes: 37 additions & 0 deletions __tests__/components/SkeletonDetail.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from "react";
import { render } from "@testing-library/react-native";

import { SkeletonDetail } from "~/components/common/SkeletonDetail";

describe("SkeletonDetail", () => {
it("renders with default props", () => {
const { getByTestId } = render(<SkeletonDetail />);
expect(getByTestId("skeleton-detail")).toBeTruthy();
});

it("renders the correct number of sections", () => {
const { getByTestId, queryByTestId } = render(<SkeletonDetail sections={2} />);
expect(getByTestId("skeleton-detail-section-0")).toBeTruthy();
expect(getByTestId("skeleton-detail-section-1")).toBeTruthy();
expect(queryByTestId("skeleton-detail-section-2")).toBeNull();
});

it("has correct accessibility attributes", () => {
const { getByTestId } = render(<SkeletonDetail />);
const root = getByTestId("skeleton-detail");
expect(root.props.accessibilityLabel).toBe("Loading detail");
expect(root.props.accessibilityRole).toBe("progressbar");
});

it("uses custom testID", () => {
const { getByTestId } = render(<SkeletonDetail testID="my-detail" />);
expect(getByTestId("my-detail")).toBeTruthy();
});

it("renders fields per section correctly", () => {
const { getByTestId } = render(<SkeletonDetail sections={1} fieldsPerSection={2} />);
const section = getByTestId("skeleton-detail-section-0");
// Section title + 2 fields = 3 children
expect(section.children.length).toBe(3);
});
});
38 changes: 38 additions & 0 deletions __tests__/components/SkeletonForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from "react";
import { render } from "@testing-library/react-native";

import { SkeletonForm } from "~/components/common/SkeletonForm";

describe("SkeletonForm", () => {
it("renders with default props", () => {
const { getByTestId } = render(<SkeletonForm />);
expect(getByTestId("skeleton-form")).toBeTruthy();
});

it("renders the correct number of fields", () => {
const { getByTestId, queryByTestId } = render(<SkeletonForm fields={3} />);
expect(getByTestId("skeleton-form-field-0")).toBeTruthy();
expect(getByTestId("skeleton-form-field-1")).toBeTruthy();
expect(getByTestId("skeleton-form-field-2")).toBeTruthy();
expect(queryByTestId("skeleton-form-field-3")).toBeNull();
});

it("has correct accessibility attributes", () => {
const { getByTestId } = render(<SkeletonForm />);
const root = getByTestId("skeleton-form");
expect(root.props.accessibilityLabel).toBe("Loading form");
expect(root.props.accessibilityRole).toBe("progressbar");
});

it("uses custom testID", () => {
const { getByTestId } = render(<SkeletonForm testID="my-form" />);
expect(getByTestId("my-form")).toBeTruthy();
});

it("each field has label and input skeletons", () => {
const { getByTestId } = render(<SkeletonForm fields={1} />);
const field = getByTestId("skeleton-form-field-0");
// Label + input = 2 children
expect(field.children.length).toBe(2);
});
});
41 changes: 41 additions & 0 deletions __tests__/components/SkeletonList.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from "react";
import { render } from "@testing-library/react-native";

import { SkeletonList } from "~/components/common/SkeletonList";

describe("SkeletonList", () => {
it("renders with default props", () => {
const { getByTestId } = render(<SkeletonList />);
expect(getByTestId("skeleton-list")).toBeTruthy();
});

it("renders the correct number of rows", () => {
const { toJSON } = render(<SkeletonList rows={3} />);
const tree = toJSON();
// Root has 3 row children
expect(tree.children).toHaveLength(3);
});

it("hides avatars when showAvatar is false", () => {
const { toJSON: withAvatar } = render(<SkeletonList rows={1} showAvatar={true} />);
const { toJSON: withoutAvatar } = render(<SkeletonList rows={1} showAvatar={false} />);
const withAvatarTree = withAvatar();
const withoutAvatarTree = withoutAvatar();
// Row with avatar has more children than without
expect(withAvatarTree.children[0].children.length).toBeGreaterThan(
withoutAvatarTree.children[0].children.length
);
});

it("has correct accessibility attributes", () => {
const { getByTestId } = render(<SkeletonList />);
const root = getByTestId("skeleton-list");
expect(root.props.accessibilityLabel).toBe("Loading list");
expect(root.props.accessibilityRole).toBe("progressbar");
});

it("uses custom testID", () => {
const { getByTestId } = render(<SkeletonList testID="custom-skeleton" />);
expect(getByTestId("custom-skeleton")).toBeTruthy();
});
});
73 changes: 73 additions & 0 deletions __tests__/components/UndoSnackbar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from "react";
import { render, fireEvent, act } from "@testing-library/react-native";

import { UndoSnackbar } from "~/components/common/UndoSnackbar";

describe("UndoSnackbar", () => {
beforeEach(() => {
jest.useFakeTimers();
});

afterEach(() => {
jest.useRealTimers();
});

it("renders when visible", () => {
const { getByTestId } = render(
<UndoSnackbar message="Item deleted" onUndo={jest.fn()} visible={true} />
);
expect(getByTestId("undo-snackbar")).toBeTruthy();
});

it("does not render when not visible", () => {
const { queryByTestId } = render(
<UndoSnackbar message="Item deleted" onUndo={jest.fn()} visible={false} />
);
expect(queryByTestId("undo-snackbar")).toBeNull();
});

it("displays the message text", () => {
const { getByTestId } = render(
<UndoSnackbar message="Record removed" onUndo={jest.fn()} visible={true} />
);
expect(getByTestId("undo-snackbar-message").props.children).toBe("Record removed");
});

it("calls onUndo when undo is pressed", () => {
const onUndo = jest.fn();
const { getByTestId } = render(
<UndoSnackbar message="Deleted" onUndo={onUndo} visible={true} />
);
fireEvent.press(getByTestId("undo-snackbar-undo"));
expect(onUndo).toHaveBeenCalledTimes(1);
});

it("auto-hides after duration", () => {
const { queryByTestId } = render(
<UndoSnackbar message="Deleted" onUndo={jest.fn()} visible={true} duration={3000} />
);
expect(queryByTestId("undo-snackbar")).toBeTruthy();

act(() => {
jest.advanceTimersByTime(3000);
});

expect(queryByTestId("undo-snackbar")).toBeNull();
});

it("has correct accessibility attributes", () => {
const { getByTestId } = render(
<UndoSnackbar message="Deleted" onUndo={jest.fn()} visible={true} />
);
const root = getByTestId("undo-snackbar");
expect(root.props.accessibilityRole).toBe("alert");
expect(root.props.accessibilityLabel).toBe("Deleted");
});

it("uses custom testID", () => {
const { getByTestId } = render(
<UndoSnackbar message="Test" onUndo={jest.fn()} visible={true} testID="my-snackbar" />
);
expect(getByTestId("my-snackbar")).toBeTruthy();
});
});
Loading
Loading