Repository: facebook/react
File: packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js (JavaScript)

Code before:
    expect(container.textContent).toEqual('Error in getSnapshot');
  });

  test('Infinite loop if getSnapshot keeps returning new reference', async () => {
    const store = createExternalStore({});

    function App() {
      const text = useSyncExternalStore(store.subscribe, () => ({}));
      return <Text text={JSON.stringify(text)} />;
    }

    const container = document.createElement('div');
    const root = createRoot(container);

    await expect(async () => {
      expect(() =>
        ReactDOM.flushSync(async () => root.render(<App />)),
      ).toThrow(
        'Maximum update depth exceeded. This can happen when a component repeatedly ' +
          'calls setState inside componentWillUpdate or componentDidUpdate. React limits ' +
          'the number of nested updates to prevent infinite loops.',
      );
    }).toErrorDev(
      'The result of getSnapshot should be cached to avoid an infinite loop',
      // Stacks don't work when mixing the source and the npm package.
      {withoutStack: gate(flags => flags.enableUseSyncExternalStoreShim)},
    );
  });

  test('getSnapshot can return NaN without infinite loop warning', async () => {
    const store = createExternalStore('not a number');

    function App() {
      const value = useSyncExternalStore(store.subscribe, () =>
        parseInt(store.getState(), 10),
      );
      return <Text text={value} />;
    }

    const container = document.createElement('div');
    const root = createRoot(container);

    // Initial render that reads a snapshot of NaN. This is OK because we use
    // Object.is algorithm to compare values.
    await act(() => root.render(<App />));
    expect(container.textContent).toEqual('NaN');

    // Update to real number
    await act(() => store.set(123));
    expect(container.textContent).toEqual('123');


Review comment (bug):
Seems like they do work in more scenarios: 

```
FAIL packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js
  ● Shared useSyncExternalStore behavior (shim and built-in) › Infinite loop if getSnapshot keeps returning new reference

    Received warning unexpectedly includes a component stack:
      "Warning: The result of getSnapshot should be cached to avoid an infinite loop
        in App (at **)"
    If this warning intentionally includes the component stack, remove {withoutStack: true} from the toErrorDev() call. If you have a mix of warnings with and without stack in one toErrorDev() call, pass {withoutStack: N} where N is the number of warnings without stacks.

      597 |           'the number of nested updates to prevent infinite loops.',
      598 |       );
    > 599 |     }).toErrorDev(
          |        ^
      600 |       'The result of getSnapshot should be cached to avoid an infinite loop',
      601 |       // Stacks don't work when mixing the source and the npm package.
      602 |       {withoutStack: gate(flags => flags.enableUseSyncExternalStoreShim)},

      at Object.<anonymous> (packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js:599:8)
```
https://app.circleci.com/pipelines/github/facebook/react/49531/workflows/85823153-d4e2-4c91-96e3-4df0ba4ab35b/jobs/755631?invite=true#step-106-1266_121

Code after:
    const store = createExternalStore({});

    function App() {
      const text = useSyncExternalStore(store.subscribe, () => ({}));
      return <Text text={JSON.stringify(text)} />;
    }

    const container = document.createElement('div');
    const root = createRoot(container);

    await expect(async () => {
      expect(() =>
        ReactDOM.flushSync(async () => root.render(<App />)),
      ).toThrow(
        'Maximum update depth exceeded. This can happen when a component repeatedly ' +
          'calls setState inside componentWillUpdate or componentDidUpdate. React limits ' +
          'the number of nested updates to prevent infinite loops.',
      );
    }).toErrorDev(
      'The result of getSnapshot should be cached to avoid an infinite loop',
      {
        withoutStack: gate(flags => {
          if (flags.enableUseSyncExternalStoreShim) {
            // Stacks don't work when mixing the source and the npm package.
            return flags.source;
          }
          return false;
        }),
      },
    );
  });

  test('getSnapshot can return NaN without infinite loop warning', async () => {
    const store = createExternalStore('not a number');

    function App() {
      const value = useSyncExternalStore(store.subscribe, () =>
        parseInt(store.getState(), 10),
      );
      return <Text text={value} />;
    }

    const container = document.createElement('div');
    const root = createRoot(container);

    // Initial render that reads a snapshot of NaN. This is OK because we use
    // Object.is algorithm to compare values.
    await act(() => root.render(<App />));
    expect(container.textContent).toEqual('NaN');

    // Update to real number