diff --git a/__tests__/components/DensitySelector.test.tsx b/__tests__/components/DensitySelector.test.tsx
new file mode 100644
index 0000000..5ee6fc5
--- /dev/null
+++ b/__tests__/components/DensitySelector.test.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+import { render, fireEvent } from "@testing-library/react-native";
+
+import { DensitySelector } from "~/components/common/DensitySelector";
+import { useUIStore } from "~/stores/ui-store";
+
+describe("DensitySelector", () => {
+ beforeEach(() => {
+ // Reset the shared store to the default before each case.
+ useUIStore.setState({ density: "comfortable" });
+ });
+
+ it("renders both density options", () => {
+ const { getByText } = render();
+ expect(getByText("Comfortable")).toBeTruthy();
+ expect(getByText("Compact")).toBeTruthy();
+ });
+
+ it("switches the global density to compact on press", () => {
+ const { getByLabelText } = render();
+ expect(useUIStore.getState().density).toBe("comfortable");
+
+ fireEvent.press(getByLabelText("Compact"));
+ expect(useUIStore.getState().density).toBe("compact");
+ });
+
+ it("toggles back to comfortable", () => {
+ useUIStore.setState({ density: "compact" });
+ const { getByLabelText } = render();
+
+ fireEvent.press(getByLabelText("Comfortable"));
+ expect(useUIStore.getState().density).toBe("comfortable");
+ });
+});
diff --git a/__tests__/components/DetailInlineSelectEdit.test.tsx b/__tests__/components/DetailInlineSelectEdit.test.tsx
new file mode 100644
index 0000000..21db80d
--- /dev/null
+++ b/__tests__/components/DetailInlineSelectEdit.test.tsx
@@ -0,0 +1,72 @@
+import React from "react";
+import { render, fireEvent } from "@testing-library/react-native";
+
+import { DetailViewRenderer } from "~/components/renderers/DetailViewRenderer";
+import type { FieldDefinition, FormViewMeta } from "~/components/renderers/types";
+
+/**
+ * Inline select/status editing on the detail screen. The editable badge is an
+ * internal component (`EditableSelectField`), so it's exercised through
+ * `DetailViewRenderer` with an `onFieldEdit` handler.
+ */
+describe("DetailViewRenderer — inline select edit", () => {
+ const record = { id: "r1", status: "todo" };
+ const fields: FieldDefinition[] = [
+ {
+ name: "status",
+ label: "Status",
+ type: "select",
+ options: [
+ { value: "todo", label: "To Do" },
+ { value: "in_progress", label: "In Progress" },
+ { value: "done", label: "Done" },
+ ],
+ },
+ ];
+ // A curated view guarantees the field renders regardless of value.
+ const view: FormViewMeta = { sections: [{ fields: ["status"] }] };
+
+ it("stays read-only (no edit affordance) without an onFieldEdit handler", () => {
+ const { getByText, queryByLabelText } = render(
+ ,
+ );
+ // The current value still shows as its option label…
+ expect(getByText("To Do")).toBeTruthy();
+ // …but there is no tappable edit control.
+ expect(queryByLabelText("Edit Status")).toBeNull();
+ });
+
+ it("opens the picker and persists a new value via onFieldEdit", () => {
+ const onFieldEdit = jest.fn().mockResolvedValue(undefined);
+ const { getByLabelText } = render(
+ ,
+ );
+
+ fireEvent.press(getByLabelText("Edit Status"));
+ fireEvent.press(getByLabelText("In Progress"));
+
+ expect(onFieldEdit).toHaveBeenCalledWith("status", "in_progress");
+ });
+
+ it("does not persist when the current value is re-selected", () => {
+ const onFieldEdit = jest.fn();
+ const { getByLabelText } = render(
+ ,
+ );
+
+ fireEvent.press(getByLabelText("Edit Status"));
+ fireEvent.press(getByLabelText("To Do")); // already the current value
+
+ expect(onFieldEdit).not.toHaveBeenCalled();
+ });
+});
diff --git a/__tests__/components/QuickCreateSheet.test.tsx b/__tests__/components/QuickCreateSheet.test.tsx
new file mode 100644
index 0000000..32ea968
--- /dev/null
+++ b/__tests__/components/QuickCreateSheet.test.tsx
@@ -0,0 +1,79 @@
+import React from "react";
+import { render, fireEvent } from "@testing-library/react-native";
+
+import "~/lib/i18n"; // initialize i18next so t() resolves (the component chain doesn't)
+import { QuickCreateSheet } from "~/components/home/QuickCreateSheet";
+import type { AppMeta } from "~/hooks/useApps";
+
+// The global expo-router mock hands back a fresh `push` per call, so override it
+// here with a shared spy we can assert on.
+const mockPush = jest.fn();
+jest.mock("expo-router", () => ({
+ useRouter: () => ({ push: mockPush, replace: jest.fn(), back: jest.fn() }),
+}));
+
+const apps: AppMeta[] = [
+ {
+ name: "todo_app",
+ label: "Todo",
+ navigation: [
+ {
+ id: "g1",
+ type: "group",
+ label: "Tasks",
+ children: [
+ { id: "n1", type: "object", label: "All Tasks", objectName: "todo_task" },
+ ],
+ },
+ // Non-object leaves are ignored by quick-create.
+ { id: "n2", type: "dashboard", label: "Dashboard", dashboardName: "d1" },
+ ],
+ },
+ {
+ name: "crm",
+ label: "CRM",
+ navigation: [
+ { id: "n3", type: "object", label: "Contacts", objectName: "contact" },
+ // Same app/object surfaced under a second view — must dedupe.
+ { id: "n4", type: "object", label: "Contacts (Kanban)", objectName: "contact" },
+ ],
+ },
+];
+
+describe("QuickCreateSheet", () => {
+ beforeEach(() => mockPush.mockClear());
+
+ it("lists creatable objects flattened from the apps' navigation, deduped", () => {
+ const { getByText, queryByText, queryAllByText } = render(
+ ,
+ );
+
+ // Object leaves (incl. those nested in a group) surface.
+ expect(getByText("All Tasks")).toBeTruthy();
+ expect(getByText("Contacts")).toBeTruthy();
+ // The dashboard leaf is not a creatable object.
+ expect(queryByText("Dashboard")).toBeNull();
+ // The second view over `contact` is deduped away.
+ expect(queryByText("Contacts (Kanban)")).toBeNull();
+ expect(queryAllByText("Contacts")).toHaveLength(1);
+ });
+
+ it("opens the object's create form and closes the sheet on press", () => {
+ const onOpenChange = jest.fn();
+ const { getByLabelText } = render(
+ ,
+ );
+
+ fireEvent.press(getByLabelText("All Tasks"));
+
+ expect(onOpenChange).toHaveBeenCalledWith(false);
+ expect(mockPush).toHaveBeenCalledWith("/(app)/todo_app/todo_task/new");
+ });
+
+ it("shows an empty hint when no app publishes a creatable object", () => {
+ const { getByText } = render(
+ ,
+ );
+ expect(getByText("No objects available to create.")).toBeTruthy();
+ });
+});