Skip to content

Conversation

dhruvisompura
Copy link
Contributor

@dhruvisompura dhruvisompura commented Aug 19, 2025

This PR adds a React unit test for the bug from #8515

The unit test is for the ColumnSummaryCell component, that verifies the null percentage display value from the Data Explorer Summary panel matches the expectations in the UI.

The test checks 4 specific null percentage formatting edge cases:

  • 0%
  • <1% - Values between 0% and 1%
  • 99% - Values between 99% and 99.9%
  • 100%

You can run the unit test in several different ways:

// run test by providing a glob based search for the file name
./scripts/test.sh --glob **/columnSummaryCell.*

// run test by providing the full path for the compiled file
./scripts/test.sh --run vs/workbench/services/positronDataExplorer/test/browser/columnSummaryCell.test.js

// run test based off the test suite name
./scripts/test.sh --grep "ColumnSummaryCell"

The react unit test setup uses Mocha + assert + Sinon.js + React instead of the now common place react-testing-library. This is to reduce introducing any additional dependencies since the number of component unit tests will be added on an as-needed basis.

  • We setup/teardown of DOM containers and React roots.
  • We render the component with ReactDOM.createRoot()
  • We query the DOM using native DOM APIs (e.g., container.querySelector)
  1. Use assert and sinon for test logic (vs RTL querying and jest mocking)

Release Notes

New Features

  • N/A

Bug Fixes

  • N/A

QA Notes

@:data-explorer
@:web
@:win

Copy link

github-actions bot commented Aug 19, 2025

E2E Tests 🚀
This PR will run tests tagged with: @:critical @:data-explorer @:web @:win

readme  valid tags

@dhruvisompura dhruvisompura requested review from seeM and nstrayer August 19, 2025 00:02
Copy link
Contributor

@seeM seeM left a comment

Choose a reason for hiding this comment

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

Great to see our first (I think) Positron React unit test! 🎉

As requested, my thoughts re mocking:

  1. Should we type our mocks?

    That way we get TS errors if we change the class/interface and forget to update the test and mocks. I've found that useful when refactoring. We also get better completions etc from LLMs and language servers.

    Unfortunately AFAIK this would also require extracting more interfaces so there is a tradeoff.

  2. I try to avoid Sinon where possible and just implement it in TS.

    This tends to give more informative and accurate TS errors while refactoring as well as more reusable mocks if we end up sharing them across test files. It doesn't use too many more lines of code.

    For example:

    const mockHoverManager: IHoverManager = {
        hideHover: () => { },
        showHover: () => { },
    };

    or

    class MockHoverManager implements IHoverManager {
        hideHover(): void { }
        showHover(target: HTMLElement, content?: string | (() => string | undefined)): void { }
    }
  3. I typically reserve Sinon for when I absolutely need to:

    1. When I have to overwrite some method or property and can't pass in a mocked implemention e.g. stub()bing parts of the vscode and positron extension APIs in extension tests.
    2. When my test requires spying and making assertions about function/method calls.
  4. Should we use act instead of setTimeout(..., 0)? Some other possibly interesting React testing ideas here: https://react.dev/reference/react/act

Once we agree on our preferences, it might be worth collecting them in the wiki for the rest of the team.

nstrayer
nstrayer previously approved these changes Aug 20, 2025
Copy link
Contributor

@nstrayer nstrayer left a comment

Choose a reason for hiding this comment

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

These are great and work well.

I will defer judgement on mocking patterns to @seeM as he's got much more thought out opinions there than I do.

I made a note about adding a helper function for waiting for an element to appear rather than using the timeout trick.
Ideally the act() function would work but I dont think we can use that here because the react we bundle with Positron is a static-prebuilt script instead of part of the larger build process. act() is disabled in "production" react builds.

In the future we'll obviously break out some of this into testing utilities but for now I think this is a great foundation!

// Wait for initial render
await new Promise(resolve => setTimeout(resolve, 0));

const nullPercentElement = container.querySelector('.text-percent');
Copy link
Contributor

Choose a reason for hiding this comment

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

Example of waiting function

Suggested change
const nullPercentElement = container.querySelector('.text-percent');
const nullPercentElement = await waitForElement(container, '.text-percent');


return mockInstance as unknown as TableSummaryDataGridInstance;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

You could get playwright-esque behavior for rendering this way as well. Maybe a bit too heavy handed though.

Suggested change
/**
* Waits for an element to appear in the DOM.
* @param container The container element to search within
* @param selector The CSS selector for the element to wait for
* @param timeout Maximum time to wait in milliseconds (default: 100ms)
* @returns Promise that resolves with the found element or rejects if timeout
*/
function waitForElement(
container: HTMLElement,
selector: string,
timeout = 250
): Promise<Element> {
return new Promise((resolve, reject) => {
// Check if element already exists
const element = container.querySelector(selector);
if (element) {
return resolve(element);
}
// Set up mutation observer to watch for the element
const observer = new MutationObserver(() => {
const el = container.querySelector(selector);
if (el) {
observer.disconnect();
resolve(el);
}
});
// Start observing DOM changes
observer.observe(container, {
// Check for changes in attributes in case the waited for element
// exists but has the selector added after the element is created
attributes: true,
childList: true,
subtree: true,
});
// Set up timeout to prevent hanging tests
setTimeout(() => {
observer.disconnect();
reject(
new Error(
`Timed out after ${timeout}ms waiting for element: ${selector}`
)
);
}, timeout);
});
}

@seeM
Copy link
Contributor

seeM commented Aug 28, 2025

Looks great!

@dhruvisompura dhruvisompura merged commit 4a712bd into main Aug 28, 2025
9 checks passed
@dhruvisompura dhruvisompura deleted the feature/add-data-explorer-react-unit-tests branch August 28, 2025 13:33
@github-actions github-actions bot locked and limited conversation to collaborators Aug 28, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants