Skip to content

Make suite.only a thin alias of suite.focus#1192

Draft
ealush wants to merge 3 commits intolatestfrom
codex/replace-suite.focus-with-suite.only-in-tests
Draft

Make suite.only a thin alias of suite.focus#1192
ealush wants to merge 3 commits intolatestfrom
codex/replace-suite.focus-with-suite.only-in-tests

Conversation

@ealush
Copy link
Owner

@ealush ealush commented Jan 20, 2026

Motivation

  • Provide a concise, chainable shorthand so callers can use suite.only(...) instead of suite.focus({ only: ... }).
  • Reduce duplicated logic by routing the external only API through the existing focus implementation.
  • Keep the chainable, runnable interface (afterEach, afterField, run) available for focused runs.

Description

  • Wire only to focus in useCreateSuiteMethods.ts by creating a focusFn and making only call focusFn({ only: match }) instead of using a separate useCreateOnly implementation.
  • Remove the dedicated useCreateOnly implementation and delete the now-obsolete only.hook.test.ts test file.
  • Update tests, snapshots, docs and website examples to prefer suite.only(...) over suite.focus({ only: ... }) and adapt types/imports to use FocusMatch where applicable.
  • Keep the existing focus implementation intact so all behavior remains consistent while providing the shorter API.

Testing

  • Ran pre-commit checks via npx lint-staged (which ran prettier and eslint --fix) and they completed successfully.
  • No full test-suite run was executed as part of this change; snapshots and tests were updated to reflect the API change.

Codex Task

@vercel
Copy link

vercel bot commented Jan 20, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
vest Ready Ready Preview, Comment Jan 20, 2026 11:16pm
vest-next Ready Ready Preview, Comment Jan 20, 2026 11:16pm

@coderabbitai
Copy link

coderabbitai bot commented Jan 20, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @ealush, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a more ergonomic and direct way to perform focused validation runs within the suite by adding a suite.only method. This new method acts as a thin alias for the existing suite.focus({ only: ... }) functionality, allowing users to specify fields or groups for validation more concisely. The change also involves significant internal refactoring to consolidate logic, ensuring consistency and reducing code duplication, while maintaining backward compatibility with the suite.focus method.

Highlights

  • New suite.only API: Introduced a new suite.only method as a concise, chainable shorthand for suite.focus({ only: ... }), streamlining focused validation runs.
  • Group Focusing Support: The suite.only method now supports focusing validation by groups in addition to individual fields, enhancing flexibility.
  • Code Refactoring and Simplification: The internal implementation of the only API has been refactored to route through the existing focus mechanism, leading to the removal of duplicated logic and the useCreateOnly implementation.
  • Documentation and Examples Update: All relevant documentation, website examples, and test snapshots have been updated to reflect the new suite.only API and its usage.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request successfully refactors suite.only to be a thin alias of suite.focus, providing a more concise and chainable API for focused validation runs. The changes include updating type definitions, modifying the suite creation logic, and updating all relevant tests, snapshots, and documentation. The introduction of group-based focusing is a valuable enhancement. However, a logical issue was identified in how useIsExcludedByInclusion interacts with the only mode, which could lead to incorrect exclusion behavior.

Comment on lines 12 to 23
export function useHasOnliedTests(
testObject: TIsolateTest,
fieldName?: TFieldName,
groupName?: TGroupName,
): boolean {
return isNotNullish(
Walker.findClosest(testObject, (child: TIsolate) => {
if (!FocusSelectors.isIsolateFocused(child)) return false;

return FocusSelectors.isOnlyFocused(child, fieldName).unwrap();
return FocusSelectors.isOnlyFocused(child, fieldName, groupName).unwrap();
}),
);

Choose a reason for hiding this comment

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

high

The useHasOnliedTests function, as modified in this PR, checks if the given testObject (or an ancestor) is only focused. However, in useIsExcludedByInclusion, this function is called after it has already been determined that the testObject is not explicitly only focused. This means useHasOnliedTests will always return false in that context.

To correctly implement the suite.only behavior (where all non-focused tests are excluded unless explicitly included), useIsExcludedByInclusion needs to know if any only isolates are active in the suite globally, not if the current test itself is only focused. The current implementation of useHasOnliedTests does not serve this purpose.

I recommend refactoring useHasOnliedTests to check for the global presence of only isolates in the suite. Alternatively, introduce a new helper function for this global check and use it in useIsExcludedByInclusion.

Here's a suggestion to introduce a new global check function and keep the existing useHasOnliedTests for its specific (though currently unused in useIsExcludedByInclusion) purpose:

import { isNotNullish } from 'vest-utils';
import { TIsolate, Walker, VestRuntime } from 'vestjs-runtime';
import { FocusModes } from './FocusedKeys';

import { TIsolateTest } from '../../core/isolate/IsolateTest/IsolateTest';
import { TFieldName, TGroupName } from '../../suiteResult/SuiteResultTypes';

import { FocusSelectors } from './focused';

/**
 * Checks if there are any 'only' focused isolates active in the current suite context.
 * This function performs a global check across the entire suite isolate tree.
 */
export function hasAnyOnlyIsolatesInSuite(): boolean {
  const currentSuiteIsolate = VestRuntime.useAvailableRoot();
  if (!currentSuiteIsolate) return false;

  return Walker.some(currentSuiteIsolate, (isolate: TIsolate) => {
    if (!FocusSelectors.isIsolateFocused(isolate)) return false;
    return isolate.data.focusMode === FocusModes.ONLY;
  });
}

/**
 * Checks if the given testObject (or any of its ancestors) is 'only' focused
 * for the specified fieldName and groupName.
 */
export function useHasOnliedTests(
  testObject: TIsolateTest,
  fieldName?: TFieldName,
  groupName?: TGroupName,
): boolean {
  return isNotNullish(
    Walker.findClosest(testObject, (child: TIsolate) => {
      if (!FocusSelectors.isIsolateFocused(child)) return false;

      return FocusSelectors.isOnlyFocused(child, fieldName, groupName).unwrap();
    }),
  );
}

Comment on lines +56 to 69
function useIsExcludedByInclusion(
testObject: TIsolateTest,
fieldName: string,
groupName?: string,
): boolean {
// If there is _ANY_ `only`ed test (and we already know this one isn't) return true
if (useHasOnliedTests(testObject)) {
// Check if inclusion rules for this field (`include` hook)
return !dynamicValue(inclusion[fieldName], testObject);
if (!useHasOnliedTests(testObject, fieldName, groupName)) {
return false;
}

// We're done here. This field is not excluded
return false;
const inclusion = useInclusion();
// Check if inclusion rules for this field (`include` hook)
return !dynamicValue(inclusion[fieldName], testObject);
}

Choose a reason for hiding this comment

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

high

The logic within useIsExcludedByInclusion is currently inverted, leading to incorrect behavior when suite.only is active.

Here's the breakdown:

  1. useIsExcludedByFocus determines that the current testObject is not explicitly only focused, and then calls useIsExcludedByInclusion.
  2. Inside useIsExcludedByInclusion, the condition if (!useHasOnliedTests(testObject, fieldName, groupName)) is evaluated. Since useHasOnliedTests (as currently implemented) checks if this specific test is only focused, and we already know it's not, useHasOnliedTests will return false.
  3. Consequently, !useHasOnliedTests(...) becomes true, causing the function to immediately return false.

This means that if only mode is active, but the current test is not among the only focused tests, it will not be excluded. This breaks the fundamental behavior of suite.only, which should exclude all tests not explicitly focused.

To fix this, useIsExcludedByInclusion should check for the global presence of any only isolates in the suite (using the hasAnyOnlyIsolatesInSuite function proposed in the useHasOnliedTests.ts comment). If global only mode is active, and the current test is not explicitly only focused (which is the precondition for calling this function), then the test should be excluded unless an include hook explicitly includes it.

function useIsExcludedByInclusion(
  testObject: TIsolateTest,
  fieldName: string,
  groupName?: string,
): boolean {
  // If there are *any* 'only' tests active in the suite, and this test is not
  // explicitly 'only' focused (as determined by useIsExcludedByFocus),
  // then this test should be excluded, unless it's explicitly included by an 'include' hook.
  if (hasAnyOnlyIsolatesInSuite()) { // Use the new global check function
    const inclusion = useInclusion();
    // Check if inclusion rules for this field (`include` hook)
    return !dynamicValue(inclusion[fieldName], testObject);
  }

  // If no 'only' tests are active globally, then this test is not excluded by 'only' logic.
  return false;
}
Suggested change
function useIsExcludedByInclusion(
testObject: TIsolateTest,
fieldName: string,
groupName?: string,
): boolean {
// If there is _ANY_ `only`ed test (and we already know this one isn't) return true
if (useHasOnliedTests(testObject)) {
// Check if inclusion rules for this field (`include` hook)
return !dynamicValue(inclusion[fieldName], testObject);
if (!useHasOnliedTests(testObject, fieldName, groupName)) {
return false;
}
// We're done here. This field is not excluded
return false;
const inclusion = useInclusion();
// Check if inclusion rules for this field (`include` hook)
return !dynamicValue(inclusion[fieldName], testObject);
}
function useIsExcludedByInclusion(
testObject: TIsolateTest,
fieldName: string,
groupName?: string,
): boolean {
// If there are *any* 'only' tests active in the suite, and this test is not
// explicitly 'only' focused (as determined by useIsExcludedByFocus),
// then this test should be excluded, unless it's explicitly included by an 'include' hook.
if (hasAnyOnlyIsolatesInSuite()) {
const inclusion = useInclusion();
// Check if inclusion rules for this field (`include` hook)
return !dynamicValue(inclusion[fieldName], testObject);
}
// If no 'only' tests are active globally, then this test is not excluded by 'only' logic.
return false;
}

@github-actions
Copy link

github-actions bot commented Jan 20, 2026

🚀 Benchmark Results

Suite Benchmark Ops/sec (Hz) P99 (ms) Margin of Error Diff (Abs) Diff (%)
Reconciler & History Diffing Reconciler (Stable List) 2.782 365.56 0.97% 0 0.00%
Reconciler & History Diffing Reconciler (Full Invalidation) 2.852 356.36 0.52% 0 0.00%
Reconciler & History Diffing Reconciler (Prepend Item) 2.849 360.37 0.74% 0 0.00%
Reconciler & History Diffing Reconciler (Append Item) 2.834 355.44 0.30% 0 0.00%
Reconciler & History Diffing Reconciler (Interleaved) 2.823 357.63 0.34% 0 0.00%
Reconciler & History Diffing Isolate Reordering (Reverse) 2.831 356.84 0.53% 0 0.00%
Reconciler & History Diffing Isolate Reordering (Shuffle) 2.826 355.31 0.28% 0 0.00%
Reconciler & History Diffing Orphan GC Pressure 5.46 184.06 0.34% 0 0.00%
Result Selectors & Reporting hasErrors (Volume) 686.82 1.9861 1.11% 0 0.00%
Result Selectors & Reporting getErrors (Group Lookup) 419.03 2.8168 0.71% 0 0.00%
Result Selectors & Reporting Summary Generation (Large) 2.414 422.45 0.70% 0 0.00%
Async & Concurrency Stress Pending Storm (Memory) 2.751 437.77 5.21% 0 0.00%
Async & Concurrency Stress Resolve Storm (Throughput) 2.848 353.75 0.40% 0 0.00%
Async & Concurrency Stress Reject Storm 2.825 359.34 0.62% 0 0.00%
Async & Concurrency Stress Async Race 146.62 8.7911 2.95% 0 0.00%
Control Flow & Hooks Internals test.memo (Thrashing) 139.81 8.7698 2.20% 0 0.00%
Control Flow & Hooks Internals test.memo (Stagnation) 756.08 2.6695 1.71% 0 0.00%
Control Flow & Hooks Internals omitWhen (Active) 4.072 247.62 0.42% 0 0.00%
Control Flow & Hooks Internals skipWhen (Active) 8 127.9 0.67% 0 0.00%
Control Flow & Hooks Internals only Starvation (Early) 2.882 349.16 0.34% 0 0.00%
Control Flow & Hooks Internals only Starvation (Late) 2.9 351.05 0.47% 0 0.00%
VestBus & Internals Bus Scaling 169.44 7.3277 2.03% 0 0.00%
VestBus & Internals State Refill 117.42 11.3612 2.42% 0 0.00%
Memory & Object Lifecycle Test Object Allocator 5.745 175.13 0.32% 0 0.00%
Memory & Object Lifecycle Garbage Collection Friendly 5.721 176.92 0.43% 0 0.00%
Serialization Serialize (Large) 173.92 13.1733 4.55% 0 0.00%
Serialization Deserialize (Large) 93.048 16.0889 3.31% 0 0.00%
Edge Cases & Integration Broad Group 2.908 351.29 0.64% 0 0.00%
Edge Cases & Integration Namespace Collision 2.896 349.2 0.39% 0 0.00%
Edge Cases & Integration Large Field Names 165.33 7.4996 2.15% 0 0.00%
Edge Cases & Integration Large Failure Messages 399.56 4.5341 3.01% 0 0.00%
Complex Data Validation Enforce Huge String 250.17 9.5199 6.32% 0 0.00%
State Management Serialize Large 311.96 3.9365 2.65% 0 0.00%
Integration & Edge Cases Callback Overhead 2.96 346.83 0.91% 0 0.00%
Reordering & Reconciliation each (Reorder - Reverse) 102.27 12.9518 4.01% 0 0.00%
Reordering & Reconciliation each (Reorder - Insert Middle) 92.424 16.7215 5.11% 0 0.00%
Reordering & Reconciliation each (Reorder - Delete Middle) 101.63 15.3968 4.08% 0 0.00%
Reordering & Reconciliation each (Key Thrashing) 241.12 7.0019 4.06% 0 0.00%
State Mutation & Reset suite.remove() (Many Fields) 159.78 7.9553 1.54% 0 0.00%
State Mutation & Reset suite.reset() (Memory Reclamation) 5.744 183.62 1.44% 0 0.00%
Concurrency & Events Bus Stress 2.929 345.39 0.53% 0 0.00%
Feature Coverage Matrix enforce matrix (small payload) 435.97 4.9135 6.18% 0 0.00%
Feature Coverage Matrix enforce matrix (larger payload) 662.15 7.7732 9.66% 0 0.00%
Feature Coverage Matrix flow control eager mode 349.82 7.584 8.12% 0 0.00%
Feature Coverage Matrix flow control one mode 279.63 6.7391 6.21% 0 0.00%
Core Test Functionality test (High Volume, Same Name) 2.941 348.03 0.98% 0 0.00%
Core Test Functionality test (High Volume, Unique Names) 2.838 356.99 0.68% 0 0.00%
Nested Fields with Hooks depth 3 with 40 fields per level 9.115 119.77 7.68% 0 0.00%
Nested Fields with Hooks depth 4 with 60 fields per level 4.541 227.87 8.27% 0 0.00%
Nested Fields with Hooks depth 5 with 80 fields per level 3.896 258.11 7.17% 0 0.00%
Complex Feature Mix full run with feature flags 126.45 16.1833 8.12% 0 0.00%
Complex Feature Mix focused/conditional run 195.83 9.7045 3.22% 0 0.00%
Deep Nesting Stress depth 10 68.741 51.4287 8.74% 0 0.00%
Deep Nesting Stress depth 50 23.939 52.182 2.81% 0 0.00%
Deep Nesting Stress depth 100 14.731 69.3946 0.63% 0 0.00%
Complex Combinations & Edge Cases High Frequency test Creation 153.9 9.3865 2.88% 0 0.00%
Conditional isolates skip even indices 491.13 4.2105 6.89% 0 0.00%
Conditional isolates omit multiples of 4 473.37 4.4813 8.34% 0 0.00%
Field Volume Stress 10 fields 337.49 8.5902 7.45% 0 0.00%
Field Volume Stress 500 fields 3.419 295.43 0.44% 0 0.00%
Field Volume Stress 1000 fields 1.45 695.5 0.32% 0 0.00%
Dynamic each and groups longer list 248.44 5.9568 8.61% 0 0.00%
Raw Output
See CI logs for full output

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant