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
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ jobs:
yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}"

build-ios:
runs-on: macos-latest
# Pinned: macos-latest is mid-migration to macOS 26, whose simulator set
# has no iPhone 16 Pro on an iOS >=26 runtime, breaking the harness boot.
runs-on: macos-15
Comment thread
mfazekas marked this conversation as resolved.
env:
XCODE_VERSION: 26.3
TURBO_CACHE_DIR: .turbo/ios
Expand Down Expand Up @@ -208,7 +210,9 @@ jobs:
yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}"

test-harness-ios:
runs-on: macos-latest
# Pinned to macos-15 — see build-ios. macos-26 runners lack a matching
# iPhone 16 Pro / iOS >=26 simulator, so the boot step fails intermittently.
runs-on: macos-15
timeout-minutes: 60
env:
XCODE_VERSION: 26.3
Expand Down
40 changes: 29 additions & 11 deletions example/__tests__/use-rive-trigger.harness.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,17 @@ type TriggerContext = {
triggerFn: (() => void) | null;
error: Error | null;
renderCount: number;
rerender: (() => void) | null;
};

function createTriggerContext(): TriggerContext {
return { triggerCount: 0, triggerFn: null, error: null, renderCount: 0 };
return {
triggerCount: 0,
triggerFn: null,
error: null,
renderCount: 0,
rerender: null,
};
}

// ─── Test component: stable callback ───────────────────────────────
Expand Down Expand Up @@ -110,14 +117,19 @@ function UnstableTriggerComponent({
context.error = error;
}, [context, trigger, error]);

// Force re-renders to change callback identity
// Re-render on demand so the test can change the callback identity deterministically.
useEffect(() => {
context.rerender = () => setTick((t) => t + 1);
}, [context]);

// Regression guard: block the JS thread at mount (as RiveView init does) to starve any
// re-render driver. The old setInterval-based version flaked here (renderCount stuck at 2);
// the deterministic re-renders above survive it.
useEffect(() => {
const interval = setInterval(() => setTick((t) => t + 1), 50);
const timeout = setTimeout(() => clearInterval(interval), 300);
return () => {
clearInterval(interval);
clearTimeout(timeout);
};
const end = Date.now() + 500;
while (Date.now() < end) {
/* simulate heavy mount-time work */
}
Comment thread
mfazekas marked this conversation as resolved.
}, []);

return (
Expand Down Expand Up @@ -182,14 +194,20 @@ describe('useRiveTrigger hook', () => {
/>
);

// Wait for the re-render burst to complete (300ms of re-renders every 50ms)
await waitFor(
() => {
expect(context.renderCount).toBeGreaterThanOrEqual(3);
expect(context.rerender).not.toBeNull();
},
{ timeout: 2000 }
{ timeout: 3000 }
);

// Re-render several times to churn the callback identity.
for (let i = 0; i < 3; i++) {
context.rerender!();
await new Promise((r) => setTimeout(r, 0));
}
expect(context.renderCount).toBeGreaterThanOrEqual(3);

await waitFor(
() => {
expect(context.triggerFn).not.toBeNull();
Expand Down
Loading