1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @emails react-core
    
  8.  *
    
  9.  * @jest-environment node
    
  10.  */
    
  11. 
    
  12. 'use strict';
    
  13. 
    
  14. let React;
    
  15. let ReactNoop;
    
  16. let Scheduler;
    
  17. let useSyncExternalStore;
    
  18. let useSyncExternalStoreWithSelector;
    
  19. let act;
    
  20. let assertLog;
    
  21. 
    
  22. // This tests the userspace shim of `useSyncExternalStore` in a server-rendering
    
  23. // (Node) environment
    
  24. describe('useSyncExternalStore (userspace shim, server rendering)', () => {
    
  25.   beforeEach(() => {
    
  26.     jest.resetModules();
    
  27. 
    
  28.     // Remove useSyncExternalStore from the React imports so that we use the
    
  29.     // shim instead. Also removing startTransition, since we use that to detect
    
  30.     // outdated 18 alphas that don't yet include useSyncExternalStore.
    
  31.     //
    
  32.     // Longer term, we'll probably test this branch using an actual build of
    
  33.     // React 17.
    
  34.     jest.mock('react', () => {
    
  35.       const {
    
  36.         // eslint-disable-next-line no-unused-vars
    
  37.         startTransition: _,
    
  38.         // eslint-disable-next-line no-unused-vars
    
  39.         useSyncExternalStore: __,
    
  40.         ...otherExports
    
  41.       } = jest.requireActual('react');
    
  42.       return otherExports;
    
  43.     });
    
  44. 
    
  45.     jest.mock('use-sync-external-store/shim', () =>
    
  46.       jest.requireActual('use-sync-external-store/shim/index.native'),
    
  47.     );
    
  48. 
    
  49.     React = require('react');
    
  50.     ReactNoop = require('react-noop-renderer');
    
  51.     Scheduler = require('scheduler');
    
  52.     act = require('internal-test-utils').act;
    
  53. 
    
  54.     const InternalTestUtils = require('internal-test-utils');
    
  55.     assertLog = InternalTestUtils.assertLog;
    
  56. 
    
  57.     if (gate(flags => flags.source)) {
    
  58.       // The `shim/with-selector` module composes the main
    
  59.       // `use-sync-external-store` entrypoint. In the compiled artifacts, this
    
  60.       // is resolved to the `shim` implementation by our build config, but when
    
  61.       // running the tests against the source files, we need to tell Jest how to
    
  62.       // resolve it. Because this is a source module, this mock has no affect on
    
  63.       // the build tests.
    
  64.       jest.mock('use-sync-external-store/src/useSyncExternalStore', () =>
    
  65.         jest.requireActual('use-sync-external-store/shim'),
    
  66.       );
    
  67.       jest.mock('use-sync-external-store/src/isServerEnvironment', () =>
    
  68.         jest.requireActual(
    
  69.           'use-sync-external-store/src/forks/isServerEnvironment.native',
    
  70.         ),
    
  71.       );
    
  72.     }
    
  73.     useSyncExternalStore =
    
  74.       require('use-sync-external-store/shim').useSyncExternalStore;
    
  75.     useSyncExternalStoreWithSelector =
    
  76.       require('use-sync-external-store/shim/with-selector').useSyncExternalStoreWithSelector;
    
  77.   });
    
  78. 
    
  79.   function Text({text}) {
    
  80.     Scheduler.log(text);
    
  81.     return text;
    
  82.   }
    
  83. 
    
  84.   function createExternalStore(initialState) {
    
  85.     const listeners = new Set();
    
  86.     let currentState = initialState;
    
  87.     return {
    
  88.       set(text) {
    
  89.         currentState = text;
    
  90.         ReactNoop.batchedUpdates(() => {
    
  91.           listeners.forEach(listener => listener());
    
  92.         });
    
  93.       },
    
  94.       subscribe(listener) {
    
  95.         listeners.add(listener);
    
  96.         return () => listeners.delete(listener);
    
  97.       },
    
  98.       getState() {
    
  99.         return currentState;
    
  100.       },
    
  101.       getSubscriberCount() {
    
  102.         return listeners.size;
    
  103.       },
    
  104.     };
    
  105.   }
    
  106. 
    
  107.   test('native version', async () => {
    
  108.     const store = createExternalStore('client');
    
  109. 
    
  110.     function App() {
    
  111.       const text = useSyncExternalStore(
    
  112.         store.subscribe,
    
  113.         store.getState,
    
  114.         () => 'server',
    
  115.       );
    
  116.       return <Text text={text} />;
    
  117.     }
    
  118. 
    
  119.     const root = ReactNoop.createRoot();
    
  120.     await act(() => {
    
  121.       root.render(<App />);
    
  122.     });
    
  123.     assertLog(['client']);
    
  124.     expect(root).toMatchRenderedOutput('client');
    
  125.   });
    
  126. 
    
  127.   // @gate !(enableUseRefAccessWarning && __DEV__)
    
  128.   test('Using isEqual to bailout', async () => {
    
  129.     const store = createExternalStore({a: 0, b: 0});
    
  130. 
    
  131.     function A() {
    
  132.       const {a} = useSyncExternalStoreWithSelector(
    
  133.         store.subscribe,
    
  134.         store.getState,
    
  135.         null,
    
  136.         state => ({a: state.a}),
    
  137.         (state1, state2) => state1.a === state2.a,
    
  138.       );
    
  139.       return <Text text={'A' + a} />;
    
  140.     }
    
  141.     function B() {
    
  142.       const {b} = useSyncExternalStoreWithSelector(
    
  143.         store.subscribe,
    
  144.         store.getState,
    
  145.         null,
    
  146.         state => {
    
  147.           return {b: state.b};
    
  148.         },
    
  149.         (state1, state2) => state1.b === state2.b,
    
  150.       );
    
  151.       return <Text text={'B' + b} />;
    
  152.     }
    
  153. 
    
  154.     function App() {
    
  155.       return (
    
  156.         <>
    
  157.           <A />
    
  158.           <B />
    
  159.         </>
    
  160.       );
    
  161.     }
    
  162. 
    
  163.     const root = ReactNoop.createRoot();
    
  164.     await act(() => root.render(<App />));
    
  165. 
    
  166.     assertLog(['A0', 'B0']);
    
  167.     expect(root).toMatchRenderedOutput('A0B0');
    
  168. 
    
  169.     // Update b but not a
    
  170.     await act(() => {
    
  171.       store.set({a: 0, b: 1});
    
  172.     });
    
  173.     // Only b re-renders
    
  174.     assertLog(['B1']);
    
  175.     expect(root).toMatchRenderedOutput('A0B1');
    
  176. 
    
  177.     // Update a but not b
    
  178.     await act(() => {
    
  179.       store.set({a: 1, b: 1});
    
  180.     });
    
  181.     // Only a re-renders
    
  182.     assertLog(['A1']);
    
  183.     expect(root).toMatchRenderedOutput('A1B1');
    
  184.   });
    
  185. });