Skip to content

Fix flaky TextInput and AutocompletePrompt tests under React 19#6922

Merged
tewson merged 2 commits intofeature/react-19-ink-6from
feature/react-19-ink-6-fix-flaky-tests
Mar 3, 2026
Merged

Fix flaky TextInput and AutocompletePrompt tests under React 19#6922
tewson merged 2 commits intofeature/react-19-ink-6from
feature/react-19-ink-6-fix-flaky-tests

Conversation

@tewson
Copy link
Member

@tewson tewson commented Mar 2, 2026

Summary

  • Replace async useEffect cursor clamping in TextInput with synchronous clamping during render to eliminate a stale cursorOffset race condition exposed by React 19's scheduler changes
  • Make AutocompletePrompt DELETE-related test assertions wait for deterministic content (sendInputAndWaitForContent) instead of any frame change (sendInputAndWaitForChange)

Test plan

  • TextInput.test.tsx — all 12 tests pass
  • AutocompletePrompt.test.tsx — all 13 tests pass
  • Verify CI no longer shows flaky snapshot mismatches for these tests

🤖 Generated with Claude Code

@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

Coverage report

St.
Category Percentage Covered / Total
🟡 Statements 78.8% 14516/18421
🟡 Branches 73.11% 7216/9870
🟡 Functions 79.03% 3693/4673
🟡 Lines 79.13% 13716/17333

Test suite run success

3785 tests passing in 1448 suites.

Report generated by 🧪jest coverage report action from c855e17

@tewson
Copy link
Member Author

tewson commented Mar 2, 2026

/snapit

@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

🫰✨ Thanks @tewson! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

npm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20260302130653

Caution

After installing, validate the version by running shopify version in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

@tewson tewson marked this pull request as ready for review March 2, 2026 15:25
@tewson tewson requested a review from a team as a code owner March 2, 2026 15:25
@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

We detected some changes at packages/*/src and there are no updates in the .changeset.
If the changes are user-facing, run pnpm changeset add to track your changes and include them in the next release CHANGELOG.

Caution

DO NOT create changesets for features which you do not wish to be included in the public changelog of the next CLI release.

@tewson tewson requested a review from isaacroldan March 2, 2026 15:26
@tewson tewson force-pushed the feature/react-19-ink-6 branch from bec41c5 to 6cd5658 Compare March 2, 2026 15:36
@tewson tewson force-pushed the feature/react-19-ink-6-fix-flaky-tests branch from 5f084c2 to 662837c Compare March 2, 2026 15:36
@tewson
Copy link
Member Author

tewson commented Mar 2, 2026

/snapit

@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

🫰✨ Thanks @tewson! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

npm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20260302153804

Caution

After installing, validate the version by running shopify version in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

Replace async useEffect cursor clamping in TextInput with synchronous
clamping during render to eliminate stale cursorOffset race condition
exposed by React 19 scheduler changes. Also make AutocompletePrompt
DELETE tests wait for deterministic content instead of any frame change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tewson tewson force-pushed the feature/react-19-ink-6-fix-flaky-tests branch from 662837c to 4518206 Compare March 2, 2026 15:44
const clampedCursorOffset = Math.min(cursorOffset, (originalValue || '').length)
if (clampedCursorOffset !== cursorOffset) {
setCursorOffset(clampedCursorOffset)
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Render-phase state update (setCursorOffset) may cause React warnings or render loops

TextInput clamps cursor offset by calling setCursorOffset synchronously during render:

const clampedCursorOffset = Math.min(cursorOffset, (originalValue || '').length)
if (clampedCursorOffset !== cursorOffset) {
  setCursorOffset(clampedCursorOffset)
}

Updating state during render is an anti-pattern and can cause React warnings/errors (e.g., "Cannot update a component while rendering..."), extra render passes, and potential render loops in edge cases. It’s also more unpredictable under React 18/19 concurrent scheduling—exactly the environment this PR is targeting. The goal (avoid stale offsets reaching useInput) is valid, but can be achieved without render-phase mutation by using the clamped value for computations and syncing state in an effect/layout effect.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this relevant @tewson ? or can we ignore it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Supposedly this can be ignored because this version of Ink doesn't use concurrent mode. But newer versions support concurrent mode so I made a change in c855e17 to make it safer.

@binks-code-reviewer
Copy link

binks-code-reviewer bot commented Mar 2, 2026

🤖 Code Review · #projects-dev-ai for questions
React with 👍/👎 or reply — all feedback helps improve the agent.

Complete - No issues

📋 History

✅ 1 findings → ✅ No issues

@tewson
Copy link
Member Author

tewson commented Mar 2, 2026

/snapit

@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

🫰✨ Thanks @tewson! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

npm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20260302160425

Caution

After installing, validate the version by running shopify version in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

@tewson tewson requested a review from gonzaloriestra March 2, 2026 17:19
@tewson
Copy link
Member Author

tewson commented Mar 2, 2026

Tested with shopify kitchen-sink and everything looked fine

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

Differences in type declarations

We detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:

  • Some seemingly private modules might be re-exported through public modules.
  • If the branch is behind main you might see odd diffs, rebase main into this branch.

New type declarations

We found no new type declarations in this PR

Existing type declarations

packages/cli-kit/dist/private/node/testing/ui.d.ts
@@ -73,7 +73,8 @@ export declare function sendInputAndWait(renderInstance: ReturnType<typeof rende
 export declare function sendInputAndWaitForContent(renderInstance: ReturnType<typeof render>, content: string, ...inputs: string[]): Promise<void>;
 /** Function that is useful when you want to check the last frame of a component that unmounted.
  *
- * The reason this function exists is that in CI Ink will clear the last frame on unmount.
+ * With Ink 6 / React 19, the output is no longer cleared on unmount,
+ * so lastFrame() consistently returns the last rendered content.
  */
 export declare function getLastFrameAfterUnmount(renderInstance: ReturnType<typeof render>): string | undefined;
 type TrackedPromise<T> = Promise<T> & {
packages/cli-kit/dist/private/node/ui/components/SelectInput.d.ts
@@ -1,8 +1,5 @@
 import React from 'react';
 import { DOMElement } from 'ink';
-declare module 'react' {
-    function forwardRef<T, TProps>(render: (props: TProps, ref: React.Ref<T>) => React.ReactElement | null): (props: TProps & React.RefAttributes<T>) => React.ReactElement | null;
-}
 export interface SelectInputProps<T> {
     items: Item<T>[];
     initialItems?: Item<T>[];
@@ -18,7 +15,8 @@ export interface SelectInputProps<T> {
     morePagesMessage?: string;
     availableLines?: number;
     onSubmit?: (item: Item<T>) => void;
-    inputFixedAreaRef?: React.RefObject<DOMElement>;
+    inputFixedAreaRef?: React.Ref<DOMElement>;
+    ref?: React.Ref<DOMElement>;
     groupOrder?: string[];
 }
 export interface Item<T> {
@@ -29,4 +27,5 @@ export interface Item<T> {
     helperText?: string;
     disabled?: boolean;
 }
-export declare const SelectInput: <T>(props: SelectInputProps<T> & React.RefAttributes<DOMElement>) => React.ReactElement | null;
\ No newline at end of file
+declare function SelectInput<T>({ items: rawItems, initialItems, onChange, enableShortcuts, focus, emptyMessage, defaultValue, highlightedTerm, loading, errorMessage, hasMorePages, morePagesMessage, availableLines, onSubmit, inputFixedAreaRef, ref, groupOrder, }: SelectInputProps<T>): React.ReactElement | null;
+export { SelectInput };
\ No newline at end of file

@tewson
Copy link
Member Author

tewson commented Mar 3, 2026

/snapit

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

🫰✨ Thanks @tewson! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

npm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20260303113119

Caution

After installing, validate the version by running shopify version in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

Copy link
Contributor

@isaacroldan isaacroldan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tophatted and everything works as expected

@tewson tewson merged commit 466e944 into feature/react-19-ink-6 Mar 3, 2026
39 of 43 checks passed
@tewson tewson deleted the feature/react-19-ink-6-fix-flaky-tests branch March 3, 2026 11:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants