diff --git a/.knowledge/design_system/foundations_and_components.md b/.knowledge/design_system/foundations_and_components.md
index bee6119..cfb28dd 100644
--- a/.knowledge/design_system/foundations_and_components.md
+++ b/.knowledge/design_system/foundations_and_components.md
@@ -85,12 +85,14 @@ result toolbar. Dismissal persists locally and the action applies the existing
`newOnly` filter contract.
Job details use a wordmark/X header, identity-first hierarchy, 32-point title,
-`Data confidence` card, and a fixed safe-area action dock. The dock exposes opening
-the original listing, sharing, saving, and reporting as four independent 44-point
-actions. `Data confidence` shows verification availability, publication time,
-source count, ordered provenance fields, and source links without inventing missing
-facts. Community and author screens are identity-led destinations, not generic
-search results with a label changed.
+`Data confidence` card, and a fixed safe-area action dock. The compact dock keeps
+opening the original listing as its persistent primary action and exposes sharing,
+saving, and reporting through a native secondary-action sheet. Every dock control
+and sheet row preserves the minimum interactive target. `Data confidence` shows
+verification availability, publication time, source count, ordered provenance
+fields, and source links without inventing missing facts. Community and author
+screens are identity-led destinations, not generic search results with a label
+changed.
Lucide React Native is the application icon language. Use the icon that matches the
responsive web surface, an explicit semantic color, and a consistent 1.8 stroke.
diff --git a/.knowledge/patterns/navigation.md b/.knowledge/patterns/navigation.md
index 4d8508f..2dab694 100644
--- a/.knowledge/patterns/navigation.md
+++ b/.knowledge/patterns/navigation.md
@@ -41,9 +41,11 @@ modal dismissal, and focus restoration. A full-screen detail experience may requ
a different native presentation from the web dialog while preserving the same
information hierarchy and source-of-truth action.
-The job-detail action dock lives outside its `ScrollView`, reports its measured
-height, and adds that height to the scroll content inset. This keeps the last source,
-tag, or similar job reachable above the bottom safe area.
+The job-detail action dock lives outside its `ScrollView` as a compact single row,
+reports its measured height, and adds that height to the scroll content inset. Its
+native action sheet owns sharing, saving, and reporting without increasing the
+persistent dock height. This keeps the last source, tag, or similar job reachable
+above the bottom safe area.
Deep links and outbound GitHub links are security boundaries. Internal routes encode
stable IDs, repositories, and handles. External actions accept deliberate HTTPS
diff --git a/docs/superpowers/plans/2026-09-23-compact-job-detail-actions-implementation.md b/docs/superpowers/plans/2026-09-23-compact-job-detail-actions-implementation.md
new file mode 100644
index 0000000..631d9ec
--- /dev/null
+++ b/docs/superpowers/plans/2026-09-23-compact-job-detail-actions-implementation.md
@@ -0,0 +1,243 @@
+# Compact Job Detail Actions Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Replace the four-button job-detail footer with a compact primary action and a native secondary-action sheet.
+
+**Architecture:** `JobDetailActions` continues to own dock presentation and height measurement while adding local modal visibility. `JobDetailsScreen` keeps all product callbacks and receives no new behavioral responsibility; typed localization supplies the two new labels across all six catalogs.
+
+**Tech Stack:** React Native 0.86, Expo SDK 57, TypeScript, NativeWind, React Native Testing Library, Jest, Lucide React Native.
+
+---
+
+## File map
+
+- Modify `src/components/job-detail-actions/index.tsx`: render the compact dock and own the secondary-action modal.
+- Modify `src/app/jobs/details/index.tsx`: pass the new localized action and close labels.
+- Modify `src/i18n/types.ts`: extend the typed jobs message contract.
+- Modify `src/i18n/messages.ts`: add translations for `actions` and `closeActions` in all six locale extensions.
+- Modify `tests/components/job-detail-actions.test.tsx`: define compact dock, modal, callback, saved-state, dismissal, and measurement behavior.
+- Modify `tests/app/job-details.test.tsx`: verify the screen opens the action sheet before sharing.
+- Modify `tests/i18n/messages.test.ts`: require both new labels in every locale.
+- Modify `.knowledge/design_system/foundations_and_components.md`: replace the obsolete four-independent-buttons contract.
+- Modify `.knowledge/patterns/navigation.md`: document the reduced measured action dock.
+
+### Task 1: Lock the localized contract
+
+**Files:**
+- Modify: `tests/i18n/messages.test.ts`
+- Modify: `src/i18n/types.ts`
+- Modify: `src/i18n/messages.ts`
+
+- [ ] **Step 1: Write the failing locale assertions**
+
+Extend the test-only jobs shape with `actions?: string` and `closeActions?: string`, then assert both values for every supported locale:
+
+```ts
+expect(copy?.jobs?.actions).toBeTruthy();
+expect(copy?.jobs?.closeActions).toBeTruthy();
+```
+
+- [ ] **Step 2: Run the locale test and verify the red state**
+
+Run:
+
+```bash
+npx jest tests/i18n/messages.test.ts --runInBand
+```
+
+Expected: FAIL because `jobs.actions` and `jobs.closeActions` are absent.
+
+- [ ] **Step 3: Add the typed keys and six translations**
+
+Add these fields beside the other job action labels in `src/i18n/types.ts`:
+
+```ts
+actions: string;
+closeActions: string;
+```
+
+Add locale-specific values to each native jobs extension in `src/i18n/messages.ts`:
+
+```ts
+// en
+actions: "Actions",
+closeActions: "Close job actions",
+
+// pt-BR
+actions: "Ações",
+closeActions: "Fechar ações da vaga",
+
+// es
+actions: "Acciones",
+closeActions: "Cerrar acciones del empleo",
+
+// it
+actions: "Azioni",
+closeActions: "Chiudi azioni del lavoro",
+
+// fr
+actions: "Actions",
+closeActions: "Fermer les actions du poste",
+
+// de
+actions: "Aktionen",
+closeActions: "Stellenaktionen schließen",
+```
+
+- [ ] **Step 4: Run the locale test and verify green**
+
+Run the same Jest command. Expected: all localization tests PASS.
+
+- [ ] **Step 5: Commit the localization micro-change**
+
+```bash
+git add src/i18n/types.ts src/i18n/messages.ts tests/i18n/messages.test.ts
+git commit -m "feat(i18n): add compact job action labels"
+```
+
+### Task 2: Define the compact action behavior
+
+**Files:**
+- Modify: `tests/components/job-detail-actions.test.tsx`
+- Modify: `tests/app/job-details.test.tsx`
+
+- [ ] **Step 1: Replace the legacy component expectation with closed-dock behavior**
+
+Render the component with `actions: "Actions"` and `closeActions: "Close job actions"`. Assert that `Open original listing` and `Actions` are visible, while `Share job`, `Save job`, and `Report a problem` are absent before opening the sheet.
+
+Keep the layout event assertion, using a compact sample height:
+
+```ts
+await fireEvent(screen.getByTestId("job-detail-actions"), "layout", {
+ nativeEvent: { layout: { height: 72, width: 390, x: 0, y: 0 } },
+});
+expect(onHeightChange).toHaveBeenCalledWith(72);
+```
+
+- [ ] **Step 2: Add modal interaction expectations**
+
+Open `Actions`, press each secondary row in a fresh render or reopen cycle, and verify only the corresponding callback fires. Assert the sheet disappears after every selection. Also fire `requestClose` on the modal test ID and press the localized close control.
+
+For saved state, rerender with `isSaved` and `save: "Remove saved job"`, then assert:
+
+```ts
+expect(screen.getByLabelText("Remove saved job").props.accessibilityState).toEqual({
+ selected: true,
+});
+```
+
+- [ ] **Step 3: Update the screen test to open the sheet before sharing**
+
+Change the direct share press to:
+
+```ts
+await fireEvent.press(screen.getByText("Actions"));
+await fireEvent.press(screen.getByText("Share job"));
+expect(Share.share).toHaveBeenCalled();
+```
+
+- [ ] **Step 4: Run both tests and verify the red state**
+
+```bash
+npx jest tests/components/job-detail-actions.test.tsx tests/app/job-details.test.tsx --runInBand
+```
+
+Expected: FAIL because the dock still renders four stacked actions and has no action modal.
+
+### Task 3: Implement the compact dock and action sheet
+
+**Files:**
+- Modify: `src/components/job-detail-actions/index.tsx`
+- Modify: `src/app/jobs/details/index.tsx`
+
+- [ ] **Step 1: Extend the component labels and add local visibility**
+
+Add `actions` and `closeActions` to `JobDetailActionLabels`, import `MoreHorizontal`, `X`, `Modal`, and `useState`, then own the sheet state:
+
+```ts
+const [actionsVisible, setActionsVisible] = useState(false);
+
+const runAction = (action: () => void) => {
+ setActionsVisible(false);
+ action();
+};
+```
+
+- [ ] **Step 2: Replace the stacked buttons with one horizontal row**
+
+Inside the safe-area dock, render a compact row with the existing mint original-listing button using `flex-1`, plus a bordered `Actions` trigger using `MoreHorizontal`. Preserve 44-point minimum height and the existing measured `SafeAreaView`.
+
+- [ ] **Step 3: Render the native modal sheet**
+
+Render a `Modal` with `animationType="slide"`, `presentationStyle="pageSheet"`, `visible={actionsVisible}`, `onRequestClose={() => setActionsVisible(false)}`, and `testID="job-detail-action-sheet"`. Its safe-area content contains:
+
+```tsx
+ runAction(onShare)}>{/* Share2 + labels.share */}
+ runAction(onToggleSaved)}
+>{/* Bookmark + labels.save */}
+ runAction(onReport)}>{/* CircleAlert + labels.report */}
+```
+
+Each row uses a minimum 52-point height. Add a header titled with `labels.actions` and an icon-only close control labelled with `labels.closeActions`.
+
+- [ ] **Step 4: Pass localized labels from the screen**
+
+Add these properties to the existing labels object in `src/app/jobs/details/index.tsx`:
+
+```ts
+actions: messages.jobs.actions,
+closeActions: messages.jobs.closeActions,
+```
+
+- [ ] **Step 5: Run the focused tests and verify green**
+
+Run the two-test Jest command from Task 2. Expected: both suites PASS.
+
+- [ ] **Step 6: Commit the behavior micro-change**
+
+```bash
+git add src/components/job-detail-actions/index.tsx src/app/jobs/details/index.tsx tests/components/job-detail-actions.test.tsx tests/app/job-details.test.tsx
+git commit -m "feat(ui): compact job detail actions"
+```
+
+### Task 4: Align project knowledge and verify the application
+
+**Files:**
+- Modify: `.knowledge/design_system/foundations_and_components.md`
+- Modify: `.knowledge/patterns/navigation.md`
+
+- [ ] **Step 1: Update the design-system contract**
+
+Replace the statement that the dock exposes four independent actions with a factual description of the primary original-listing button and the secondary native action sheet.
+
+- [ ] **Step 2: Update the navigation contract**
+
+Clarify that the measured dock is a compact single row and that its native sheet owns the secondary actions without changing scroll-inset behavior.
+
+- [ ] **Step 3: Run the complete repository check**
+
+```bash
+npm run check
+```
+
+Expected: lint, typecheck, all Jest suites, and Expo Doctor PASS. If Expo Doctor alone fails because of local network or unavailable CocoaPods, rerun it with network access and report the native-tooling limitation separately; do not mask application failures.
+
+- [ ] **Step 4: Inspect the final diff and working tree**
+
+```bash
+git diff --check
+git status --short
+```
+
+Expected: no whitespace errors and only the planned knowledge files remain uncommitted.
+
+- [ ] **Step 5: Commit the knowledge micro-change**
+
+```bash
+git add .knowledge/design_system/foundations_and_components.md .knowledge/patterns/navigation.md
+git commit -m "docs: document compact job action dock"
+```
+
diff --git a/docs/superpowers/specs/2026-09-23-compact-job-detail-actions-design.md b/docs/superpowers/specs/2026-09-23-compact-job-detail-actions-design.md
new file mode 100644
index 0000000..dd0b8d4
--- /dev/null
+++ b/docs/superpowers/specs/2026-09-23-compact-job-detail-actions-design.md
@@ -0,0 +1,103 @@
+# Compact Job Detail Actions
+
+## Context
+
+The job-detail screen currently presents opening the original listing, sharing,
+saving, and reporting as four full-width buttons stacked inside its fixed action
+dock. The layout makes every action appear equally important and consumes a large
+part of short mobile viewports. The action dock must remain safe-area aware and
+keep the final scroll content reachable, but it should no longer obscure so much
+of the listing.
+
+## Product decision
+
+Keep opening the original listing as the only persistent primary action. Replace
+the three stacked secondary buttons with one compact `Actions` trigger. The
+trigger opens a native bottom sheet containing sharing, saving, and reporting as
+separate rows.
+
+This preserves immediate access to the authoritative source while letting the
+user deliberately choose a secondary action without presenting four nested or
+stacked buttons. Opening the original listing remains one tap; secondary actions
+require two taps.
+
+## Dock layout
+
+The fixed dock contains one horizontal row above the bottom safe area:
+
+- a flexible mint primary button for opening the original listing;
+- a bounded secondary `Actions` button with an ellipsis icon;
+- 44-point minimum interactive height for both controls;
+- the existing paper surface and top hairline boundary;
+- compact horizontal and vertical spacing from the shared NativeWind scale.
+
+The dock continues to report its measured total height, including safe-area
+padding. The job-detail `ScrollView` continues to use that measurement for its
+bottom content inset, so the final source, tag, or similar listing remains
+reachable above the dock.
+
+## Secondary action sheet
+
+The `Actions` trigger opens a React Native modal presented as a native page sheet.
+The sheet owns a safe-area-aware paper surface, a title, an explicit close control,
+and three independent rows:
+
+1. share the job;
+2. save the job, or remove it from saved jobs when already selected;
+3. report a problem.
+
+Each row combines the existing Lucide icon, localized label, and a minimum
+52-point row height. The saved row exposes its selected state to assistive
+technology and uses both text and filled icon state, rather than color alone.
+Reporting keeps muted visual emphasis but remains a normal accessible action, not
+a destructive confirmation.
+
+Selecting an action closes the sheet before invoking its existing callback. The
+sheet also closes through its close control, Android back behavior, and the native
+modal dismissal path. Reopening it reflects the current saved state and label.
+
+## Component boundaries
+
+`JobDetailActions` remains the reusable owner of the dock, sheet visibility, and
+presentation. Its callbacks remain narrow and unchanged: the job-details screen
+continues to own external URL validation, native sharing, device-local saved
+state, and analytics.
+
+The component receives one additional localized `actions` label and one localized
+sheet-close label. No service, context, navigation, or persistence boundary
+changes. No new dependency is required.
+
+## Localization and accessibility
+
+Add the new visible labels to the typed job message contract and all six supported
+catalogs. Existing share, save/unsave, and report strings are reused. The action
+sheet title and close control receive explicit localized accessibility labels.
+
+The implementation preserves Dynamic Type, light and dark themes, logical reading
+order, 44-point targets, semantic button roles, selected state, bottom safe area,
+and Android hardware-back dismissal. Long translations may wrap inside sheet rows
+without reducing the touch target.
+
+## Testing
+
+Behavioral component tests will first describe the new contract:
+
+- only the primary action and `Actions` trigger are visible in the closed dock;
+- secondary actions are absent until the sheet opens;
+- opening and closing the sheet works through visible controls and request-close;
+- each secondary row closes the sheet and calls only its matching callback;
+- saved and unsaved states expose the correct label and accessibility state;
+- dock layout measurement continues to report the compact measured height.
+
+The job-detail screen test will verify that the new localized trigger opens the
+sheet and that the existing source and sharing behaviors remain connected. The
+complete repository check must pass after the implementation. This is a
+JavaScript presentation change and does not require regenerating native projects.
+
+## Out of scope
+
+- changing analytics event contracts;
+- changing external URL, sharing, saving, or reporting behavior;
+- adding swipe gestures or custom animation systems;
+- redesigning the job-detail content or header;
+- promoting the compact action pattern to unrelated screens.
diff --git a/src/app/jobs/details/index.tsx b/src/app/jobs/details/index.tsx
index 8f17562..c130c86 100644
--- a/src/app/jobs/details/index.tsx
+++ b/src/app/jobs/details/index.tsx
@@ -304,6 +304,8 @@ export function JobDetailsScreen({ id }: JobDetailsScreenProps): React.ReactNode
setActionsVisible(false);
const handleLayout = (event: LayoutChangeEvent) => {
onHeightChange?.(event.nativeEvent.layout.height);
};
+ const runAction = (action: () => void) => {
+ closeActions();
+ action();
+ };
return (
-
-
-
-
-
- {labels.openOriginal}
-
-
-
-
-
- {labels.share}
-
-
-
-
-
- {labels.save}
-
-
-
-
-
- {labels.report}
-
-
-
-
+ <>
+
+
+
+
+
+ {labels.openOriginal}
+
+
+ setActionsVisible(true)}
+ >
+
+
+ {labels.actions}
+
+
+
+
+
+
+
+
+
+
+
+ {labels.actions}
+
+
+
+
+
+
+ runAction(onShare)}
+ >
+
+
+ {labels.share}
+
+
+ runAction(onToggleSaved)}
+ >
+
+
+ {labels.save}
+
+
+ runAction(onReport)}
+ >
+
+
+ {labels.report}
+
+
+
+
+
+
+ >
);
}
diff --git a/src/i18n/messages.ts b/src/i18n/messages.ts
index 376c90f..beb182f 100644
--- a/src/i18n/messages.ts
+++ b/src/i18n/messages.ts
@@ -61,6 +61,8 @@ const webNativeMessages = {
privacy: "Your preferences and visit history stay on this device.",
title: "New matches for you",
},
+ actions: "Actions",
+ closeActions: "Close job actions",
reportProblem: "Report a problem",
},
},
@@ -97,6 +99,8 @@ const webNativeMessages = {
privacy: "Suas preferências e seu histórico de visitas ficam somente neste dispositivo.",
title: "Novas vagas para você",
},
+ actions: "Ações",
+ closeActions: "Fechar ações da vaga",
reportProblem: "Reportar problema",
},
},
@@ -133,6 +137,8 @@ const webNativeMessages = {
privacy: "Tus preferencias y tu historial de visitas permanecen en este dispositivo.",
title: "Nuevas coincidencias para ti",
},
+ actions: "Acciones",
+ closeActions: "Cerrar acciones del empleo",
reportProblem: "Informar de un problema",
},
},
@@ -169,6 +175,8 @@ const webNativeMessages = {
privacy: "Le preferenze e la cronologia delle visite restano su questo dispositivo.",
title: "Nuove corrispondenze per te",
},
+ actions: "Azioni",
+ closeActions: "Chiudi azioni del lavoro",
reportProblem: "Segnala un problema",
},
},
@@ -205,6 +213,8 @@ const webNativeMessages = {
privacy: "Vos préférences et votre historique de visites restent sur cet appareil.",
title: "De nouvelles correspondances pour vous",
},
+ actions: "Actions",
+ closeActions: "Fermer les actions du poste",
reportProblem: "Signaler un problème",
},
},
@@ -241,6 +251,8 @@ const webNativeMessages = {
privacy: "Deine Einstellungen und dein Besuchsverlauf bleiben auf diesem Gerät.",
title: "Neue Treffer für dich",
},
+ actions: "Aktionen",
+ closeActions: "Stellenaktionen schließen",
reportProblem: "Problem melden",
},
},
diff --git a/src/i18n/types.ts b/src/i18n/types.ts
index e40a977..411db28 100644
--- a/src/i18n/types.ts
+++ b/src/i18n/types.ts
@@ -137,6 +137,8 @@ export interface FoundationMessages {
noDescription: string;
oldest: string;
olderBadge: string;
+ actions: string;
+ closeActions: string;
openOriginal: string;
posted: string;
regions: string;
diff --git a/tests/app/job-details.test.tsx b/tests/app/job-details.test.tsx
index 64cd8bb..ccb8e8f 100644
--- a/tests/app/job-details.test.tsx
+++ b/tests/app/job-details.test.tsx
@@ -27,7 +27,7 @@ describe("JobDetailsScreen", () => {
expect(screen.getByText("React Native")).toBeTruthy();
expect(screen.getByText("Data confidence")).toBeTruthy();
expect(screen.getAllByText("Declared in source")).toHaveLength(2);
- expect(screen.getByText("Report a problem")).toBeTruthy();
+ expect(screen.queryByText("Report a problem")).toBeNull();
await waitFor(() => expect(markViewed).toHaveBeenCalledWith("gh_123"));
await fireEvent.press(screen.getByLabelText("Show jobs from Openings"));
expect(mockPush).toHaveBeenLastCalledWith("/communities/openings-dev/jobs");
@@ -35,6 +35,7 @@ describe("JobDetailsScreen", () => {
expect(mockPush).toHaveBeenLastCalledWith("/authors/alice");
await fireEvent.press(screen.getByText("Open original listing"));
expect(open).toHaveBeenCalledWith("https://github.com/openings-dev/jobs/issues/gh_123");
+ await fireEvent.press(screen.getByText("Actions"));
await fireEvent.press(screen.getByText("Share job"));
expect(Share.share).toHaveBeenCalled();
});
diff --git a/tests/components/job-detail-actions.test.tsx b/tests/components/job-detail-actions.test.tsx
index 85a5afe..f7b8f20 100644
--- a/tests/components/job-detail-actions.test.tsx
+++ b/tests/components/job-detail-actions.test.tsx
@@ -9,6 +9,37 @@ jest.mock("@react-native-async-storage/async-storage", () => ({
setItem: jest.fn(),
}));
+const LABELS = {
+ actions: "Actions",
+ closeActions: "Close job actions",
+ openOriginal: "Open original listing",
+ report: "Report a problem",
+ save: "Save job",
+ share: "Share job",
+};
+
+async function renderActions(isSaved = false) {
+ const actions = {
+ onOpenOriginal: jest.fn(),
+ onReport: jest.fn(),
+ onShare: jest.fn(),
+ onToggleSaved: jest.fn(),
+ };
+ const onHeightChange = jest.fn();
+ const screen = await render(
+
+
+ ,
+ );
+
+ return { actions, onHeightChange, screen };
+}
+
describe("JobDetailActions", () => {
beforeEach(() => {
jest.clearAllMocks();
@@ -16,43 +47,62 @@ describe("JobDetailActions", () => {
jest.mocked(AsyncStorage.setItem).mockResolvedValue();
});
- it("exposes four independent actions and reports its dock height", async () => {
- const onHeightChange = jest.fn();
- const actions = {
- onOpenOriginal: jest.fn(),
- onReport: jest.fn(),
- onShare: jest.fn(),
- onToggleSaved: jest.fn(),
- };
- const screen = await render(
-
-
- ,
- );
+ it("keeps only the primary action and action chooser in the compact dock", async () => {
+ const { actions, onHeightChange, screen } = await renderActions();
+
+ expect(screen.getByText("Open original listing")).toBeTruthy();
+ expect(screen.getByText("Actions")).toBeTruthy();
+ expect(screen.queryByText("Share job")).toBeNull();
+ expect(screen.queryByText("Save job")).toBeNull();
+ expect(screen.queryByText("Report a problem")).toBeNull();
await fireEvent(screen.getByTestId("job-detail-actions"), "layout", {
- nativeEvent: { layout: { height: 220, width: 390, x: 0, y: 0 } },
+ nativeEvent: { layout: { height: 72, width: 390, x: 0, y: 0 } },
});
- expect(onHeightChange).toHaveBeenCalledWith(220);
-
await fireEvent.press(screen.getByText("Open original listing"));
- await fireEvent.press(screen.getByText("Share job"));
- await fireEvent.press(screen.getByText("Save job"));
- await fireEvent.press(screen.getByText("Report a problem"));
+ expect(onHeightChange).toHaveBeenCalledWith(72);
expect(actions.onOpenOriginal).toHaveBeenCalledTimes(1);
- expect(actions.onShare).toHaveBeenCalledTimes(1);
- expect(actions.onToggleSaved).toHaveBeenCalledTimes(1);
- expect(actions.onReport).toHaveBeenCalledTimes(1);
+ });
+
+ it("opens and dismisses the secondary action sheet", async () => {
+ const { screen } = await renderActions();
+
+ await fireEvent.press(screen.getByText("Actions"));
+
+ expect(screen.getByText("Share job")).toBeTruthy();
+ expect(screen.getByText("Save job")).toBeTruthy();
+ expect(screen.getByText("Report a problem")).toBeTruthy();
+
+ await fireEvent.press(screen.getByLabelText("Close job actions"));
+ expect(screen.queryByText("Share job")).toBeNull();
+
+ await fireEvent.press(screen.getByText("Actions"));
+ await fireEvent(screen.getByTestId("job-detail-action-sheet"), "requestClose");
+ expect(screen.queryByText("Share job")).toBeNull();
+ });
+
+ it.each([
+ ["Share job", "onShare"],
+ ["Save job", "onToggleSaved"],
+ ["Report a problem", "onReport"],
+ ] as const)("closes the sheet after choosing %s", async (label, callback) => {
+ const { actions, screen } = await renderActions();
+
+ await fireEvent.press(screen.getByText("Actions"));
+ await fireEvent.press(screen.getByText(label));
+
+ expect(actions[callback]).toHaveBeenCalledTimes(1);
+ expect(screen.queryByText("Share job")).toBeNull();
+ });
+
+ it("exposes the saved action as selected", async () => {
+ const { screen } = await renderActions(true);
+
+ await fireEvent.press(screen.getByText("Actions"));
+
+ expect(screen.getByLabelText("Remove saved job").props.accessibilityState).toEqual({
+ selected: true,
+ });
});
});
diff --git a/tests/i18n/messages.test.ts b/tests/i18n/messages.test.ts
index ac7b355..07666f3 100644
--- a/tests/i18n/messages.test.ts
+++ b/tests/i18n/messages.test.ts
@@ -38,6 +38,8 @@ describe("localized messages", () => {
fields?: { location?: string };
title?: string;
};
+ actions?: string;
+ closeActions?: string;
newMatches?: { action?: string; title?: string };
reportProblem?: string;
};
@@ -55,6 +57,8 @@ describe("localized messages", () => {
expect(copy?.jobs?.newMatches?.action).toBeTruthy();
expect(copy?.jobs?.dataConfidence?.title).toBeTruthy();
expect(copy?.jobs?.dataConfidence?.fields?.location).toBeTruthy();
+ expect(copy?.jobs?.actions).toBeTruthy();
+ expect(copy?.jobs?.closeActions).toBeTruthy();
expect(copy?.jobs?.reportProblem).toBeTruthy();
}
});