Skip to content

Commit c0340e5

Browse files
authored
Merge pull request #43 from objectstack-ai/copilot/complete-roadmap-development-again
2 parents cbe0336 + 41a2277 commit c0340e5

82 files changed

Lines changed: 8371 additions & 119 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ROADMAP.md

Lines changed: 341 additions & 115 deletions
Large diffs are not rendered by default.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React from "react";
2+
import { render, fireEvent } from "@testing-library/react-native";
3+
4+
import { FloatingActionButton } from "~/components/common/FloatingActionButton";
5+
import type { FABAction } from "~/components/common/FloatingActionButton";
6+
7+
describe("FloatingActionButton", () => {
8+
it("renders with default props", () => {
9+
const { getByTestId } = render(<FloatingActionButton />);
10+
expect(getByTestId("fab")).toBeTruthy();
11+
expect(getByTestId("fab-button")).toBeTruthy();
12+
});
13+
14+
it("calls onPress in simple mode", () => {
15+
const onPress = jest.fn();
16+
const { getByTestId } = render(<FloatingActionButton onPress={onPress} />);
17+
fireEvent.press(getByTestId("fab-button"));
18+
expect(onPress).toHaveBeenCalledTimes(1);
19+
});
20+
21+
it("expands to show actions when pressed with actions", () => {
22+
const actions: FABAction[] = [
23+
{ id: "a1", label: "Action 1", onPress: jest.fn() },
24+
{ id: "a2", label: "Action 2", onPress: jest.fn() },
25+
];
26+
const { getByTestId, queryByTestId } = render(
27+
<FloatingActionButton actions={actions} />
28+
);
29+
30+
// Actions not visible initially
31+
expect(queryByTestId("fab-action-a1")).toBeNull();
32+
33+
// Press to expand
34+
fireEvent.press(getByTestId("fab-button"));
35+
expect(getByTestId("fab-action-a1")).toBeTruthy();
36+
expect(getByTestId("fab-action-a2")).toBeTruthy();
37+
});
38+
39+
it("calls action onPress and collapses", () => {
40+
const actionFn = jest.fn();
41+
const actions: FABAction[] = [
42+
{ id: "a1", label: "Action 1", onPress: actionFn },
43+
];
44+
const { getByTestId, queryByTestId } = render(
45+
<FloatingActionButton actions={actions} />
46+
);
47+
48+
fireEvent.press(getByTestId("fab-button"));
49+
fireEvent.press(getByTestId("fab-action-a1"));
50+
51+
expect(actionFn).toHaveBeenCalledTimes(1);
52+
// Should collapse after action press
53+
expect(queryByTestId("fab-action-a1")).toBeNull();
54+
});
55+
56+
it("has correct accessibility labels", () => {
57+
const { getByTestId } = render(<FloatingActionButton />);
58+
expect(getByTestId("fab-button").props.accessibilityRole).toBe("button");
59+
});
60+
61+
it("uses custom testID", () => {
62+
const { getByTestId } = render(<FloatingActionButton testID="my-fab" />);
63+
expect(getByTestId("my-fab")).toBeTruthy();
64+
});
65+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from "react";
2+
import { render } from "@testing-library/react-native";
3+
4+
import { SkeletonDashboard } from "~/components/common/SkeletonDashboard";
5+
6+
describe("SkeletonDashboard", () => {
7+
it("renders with default props", () => {
8+
const { getByTestId } = render(<SkeletonDashboard />);
9+
expect(getByTestId("skeleton-dashboard")).toBeTruthy();
10+
});
11+
12+
it("renders the correct number of cards", () => {
13+
const { getByTestId, queryByTestId } = render(<SkeletonDashboard cards={3} />);
14+
expect(getByTestId("skeleton-dashboard-card-0")).toBeTruthy();
15+
expect(getByTestId("skeleton-dashboard-card-1")).toBeTruthy();
16+
expect(getByTestId("skeleton-dashboard-card-2")).toBeTruthy();
17+
expect(queryByTestId("skeleton-dashboard-card-3")).toBeNull();
18+
});
19+
20+
it("has correct accessibility attributes", () => {
21+
const { getByTestId } = render(<SkeletonDashboard />);
22+
const root = getByTestId("skeleton-dashboard");
23+
expect(root.props.accessibilityLabel).toBe("Loading dashboard");
24+
expect(root.props.accessibilityRole).toBe("progressbar");
25+
});
26+
27+
it("uses custom testID", () => {
28+
const { getByTestId } = render(<SkeletonDashboard testID="my-dash" />);
29+
expect(getByTestId("my-dash")).toBeTruthy();
30+
});
31+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React from "react";
2+
import { render } from "@testing-library/react-native";
3+
4+
import { SkeletonDetail } from "~/components/common/SkeletonDetail";
5+
6+
describe("SkeletonDetail", () => {
7+
it("renders with default props", () => {
8+
const { getByTestId } = render(<SkeletonDetail />);
9+
expect(getByTestId("skeleton-detail")).toBeTruthy();
10+
});
11+
12+
it("renders the correct number of sections", () => {
13+
const { getByTestId, queryByTestId } = render(<SkeletonDetail sections={2} />);
14+
expect(getByTestId("skeleton-detail-section-0")).toBeTruthy();
15+
expect(getByTestId("skeleton-detail-section-1")).toBeTruthy();
16+
expect(queryByTestId("skeleton-detail-section-2")).toBeNull();
17+
});
18+
19+
it("has correct accessibility attributes", () => {
20+
const { getByTestId } = render(<SkeletonDetail />);
21+
const root = getByTestId("skeleton-detail");
22+
expect(root.props.accessibilityLabel).toBe("Loading detail");
23+
expect(root.props.accessibilityRole).toBe("progressbar");
24+
});
25+
26+
it("uses custom testID", () => {
27+
const { getByTestId } = render(<SkeletonDetail testID="my-detail" />);
28+
expect(getByTestId("my-detail")).toBeTruthy();
29+
});
30+
31+
it("renders fields per section correctly", () => {
32+
const { getByTestId } = render(<SkeletonDetail sections={1} fieldsPerSection={2} />);
33+
const section = getByTestId("skeleton-detail-section-0");
34+
// Section title + 2 fields = 3 children
35+
expect(section.children.length).toBe(3);
36+
});
37+
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from "react";
2+
import { render } from "@testing-library/react-native";
3+
4+
import { SkeletonForm } from "~/components/common/SkeletonForm";
5+
6+
describe("SkeletonForm", () => {
7+
it("renders with default props", () => {
8+
const { getByTestId } = render(<SkeletonForm />);
9+
expect(getByTestId("skeleton-form")).toBeTruthy();
10+
});
11+
12+
it("renders the correct number of fields", () => {
13+
const { getByTestId, queryByTestId } = render(<SkeletonForm fields={3} />);
14+
expect(getByTestId("skeleton-form-field-0")).toBeTruthy();
15+
expect(getByTestId("skeleton-form-field-1")).toBeTruthy();
16+
expect(getByTestId("skeleton-form-field-2")).toBeTruthy();
17+
expect(queryByTestId("skeleton-form-field-3")).toBeNull();
18+
});
19+
20+
it("has correct accessibility attributes", () => {
21+
const { getByTestId } = render(<SkeletonForm />);
22+
const root = getByTestId("skeleton-form");
23+
expect(root.props.accessibilityLabel).toBe("Loading form");
24+
expect(root.props.accessibilityRole).toBe("progressbar");
25+
});
26+
27+
it("uses custom testID", () => {
28+
const { getByTestId } = render(<SkeletonForm testID="my-form" />);
29+
expect(getByTestId("my-form")).toBeTruthy();
30+
});
31+
32+
it("each field has label and input skeletons", () => {
33+
const { getByTestId } = render(<SkeletonForm fields={1} />);
34+
const field = getByTestId("skeleton-form-field-0");
35+
// Label + input = 2 children
36+
expect(field.children.length).toBe(2);
37+
});
38+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from "react";
2+
import { render } from "@testing-library/react-native";
3+
4+
import { SkeletonList } from "~/components/common/SkeletonList";
5+
6+
describe("SkeletonList", () => {
7+
it("renders with default props", () => {
8+
const { getByTestId } = render(<SkeletonList />);
9+
expect(getByTestId("skeleton-list")).toBeTruthy();
10+
});
11+
12+
it("renders the correct number of rows", () => {
13+
const { toJSON } = render(<SkeletonList rows={3} />);
14+
const tree = toJSON();
15+
// Root has 3 row children
16+
expect(tree.children).toHaveLength(3);
17+
});
18+
19+
it("hides avatars when showAvatar is false", () => {
20+
const { toJSON: withAvatar } = render(<SkeletonList rows={1} showAvatar={true} />);
21+
const { toJSON: withoutAvatar } = render(<SkeletonList rows={1} showAvatar={false} />);
22+
const withAvatarTree = withAvatar();
23+
const withoutAvatarTree = withoutAvatar();
24+
// Row with avatar has more children than without
25+
expect(withAvatarTree.children[0].children.length).toBeGreaterThan(
26+
withoutAvatarTree.children[0].children.length
27+
);
28+
});
29+
30+
it("has correct accessibility attributes", () => {
31+
const { getByTestId } = render(<SkeletonList />);
32+
const root = getByTestId("skeleton-list");
33+
expect(root.props.accessibilityLabel).toBe("Loading list");
34+
expect(root.props.accessibilityRole).toBe("progressbar");
35+
});
36+
37+
it("uses custom testID", () => {
38+
const { getByTestId } = render(<SkeletonList testID="custom-skeleton" />);
39+
expect(getByTestId("custom-skeleton")).toBeTruthy();
40+
});
41+
});
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React from "react";
2+
import { render, fireEvent, act } from "@testing-library/react-native";
3+
4+
import { UndoSnackbar } from "~/components/common/UndoSnackbar";
5+
6+
describe("UndoSnackbar", () => {
7+
beforeEach(() => {
8+
jest.useFakeTimers();
9+
});
10+
11+
afterEach(() => {
12+
jest.useRealTimers();
13+
});
14+
15+
it("renders when visible", () => {
16+
const { getByTestId } = render(
17+
<UndoSnackbar message="Item deleted" onUndo={jest.fn()} visible={true} />
18+
);
19+
expect(getByTestId("undo-snackbar")).toBeTruthy();
20+
});
21+
22+
it("does not render when not visible", () => {
23+
const { queryByTestId } = render(
24+
<UndoSnackbar message="Item deleted" onUndo={jest.fn()} visible={false} />
25+
);
26+
expect(queryByTestId("undo-snackbar")).toBeNull();
27+
});
28+
29+
it("displays the message text", () => {
30+
const { getByTestId } = render(
31+
<UndoSnackbar message="Record removed" onUndo={jest.fn()} visible={true} />
32+
);
33+
expect(getByTestId("undo-snackbar-message").props.children).toBe("Record removed");
34+
});
35+
36+
it("calls onUndo when undo is pressed", () => {
37+
const onUndo = jest.fn();
38+
const { getByTestId } = render(
39+
<UndoSnackbar message="Deleted" onUndo={onUndo} visible={true} />
40+
);
41+
fireEvent.press(getByTestId("undo-snackbar-undo"));
42+
expect(onUndo).toHaveBeenCalledTimes(1);
43+
});
44+
45+
it("auto-hides after duration", () => {
46+
const { queryByTestId } = render(
47+
<UndoSnackbar message="Deleted" onUndo={jest.fn()} visible={true} duration={3000} />
48+
);
49+
expect(queryByTestId("undo-snackbar")).toBeTruthy();
50+
51+
act(() => {
52+
jest.advanceTimersByTime(3000);
53+
});
54+
55+
expect(queryByTestId("undo-snackbar")).toBeNull();
56+
});
57+
58+
it("has correct accessibility attributes", () => {
59+
const { getByTestId } = render(
60+
<UndoSnackbar message="Deleted" onUndo={jest.fn()} visible={true} />
61+
);
62+
const root = getByTestId("undo-snackbar");
63+
expect(root.props.accessibilityRole).toBe("alert");
64+
expect(root.props.accessibilityLabel).toBe("Deleted");
65+
});
66+
67+
it("uses custom testID", () => {
68+
const { getByTestId } = render(
69+
<UndoSnackbar message="Test" onUndo={jest.fn()} visible={true} testID="my-snackbar" />
70+
);
71+
expect(getByTestId("my-snackbar")).toBeTruthy();
72+
});
73+
});

0 commit comments

Comments
 (0)