@testing-library/react#renderHook TypeScript Examples

The following examples show how to use @testing-library/react#renderHook. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: react-celo-provider.test.tsx    From react-celo with MIT License 6 votes vote down vote up
function renderHookInCKProvider<R>(
  hook: (i: unknown) => R,
  { providerProps }: RenderArgs
) {
  return renderHook<R, unknown>(hook, {
    wrapper: ({ children }) => {
      const props = { ...defaultProps, ...providerProps };
      return <CeloProvider {...props}>{children}</CeloProvider>;
    },
  });
}
Example #2
Source File: useBuckets.test.tsx    From firebase-tools-ui with Apache License 2.0 6 votes vote down vote up
describe('useBuckets', () => {
  async function setup() {
    const history = createMemoryHistory({
      initialEntries: ['/storage/' + initialBucketName],
    });

    mockBuckets(buckets);
    const Wrapper: React.FC<React.PropsWithChildren<unknown>> = ({
      children,
    }) => {
      return (
        <Router history={history}>
          <Route exact path={storagePath + `:bucket/:path*`}>
            <TestStorageProvider>
              <Suspense fallback={'lol'}>{children}</Suspense>
            </TestStorageProvider>
          </Route>
        </Router>
      );
    };
    const { result } = renderHook(() => useBuckets(), {
      wrapper: Wrapper,
    });

    await waitFor(() => delay(100));

    return { buckets: result.current };
  }

  it('combines buckets from server and from URL', async () => {
    const { buckets } = await setup();
    expect(buckets).toEqual(['blinchik', 'pelmeni', initialBucketName]);
  });
});
Example #3
Source File: useSelectedFile.test.tsx    From firebase-tools-ui with Apache License 2.0 6 votes vote down vote up
describe('useSelectedFile', () => {
  const file1 = { fullPath: 'pirojok' } as StorageFile;
  const file2 = { fullPath: 'buter' } as StorageFile;
  const file3 = { fullPath: 'notreal' } as StorageFile;
  const files = [file1, file2];

  it('allows to set a file', async () => {
    const { result } = renderHook(() => useSelectedFile(files));
    expect(result.current[0]).toBeUndefined();
    act(() => {
      result.current[1](file1);
    });
    expect(result.current[0]).toBe(file1);
  });

  it('ignores the file if it is not in the list', async () => {
    const { result } = renderHook(() => useSelectedFile(files));

    act(() => {
      result.current[1](file3);
    });

    expect(result.current[0]).toBeUndefined();
  });
});
Example #4
Source File: property-path-hooks.spec.tsx    From utopia with MIT License 6 votes vote down vote up
function getBackgroundColorHookResult(
  backgroundColorExpressions: Array<string>,
  targetPath: string[],
  spiedProps: Array<any>,
) {
  const propses = backgroundColorExpressions.map(
    (expression) => getPropsForStyleProp(expression, ['myStyleOuter', 'myStyleInner'])!,
  )

  const contextProvider = makeInspectorHookContextProvider(
    [],
    propses,
    targetPath,
    spiedProps,
    [],
    [],
  ) // FIXME This should be using computed styles

  const { result } = renderHook(
    () =>
      useInspectorInfo(
        backgroundLonghandPaths,
        backgroundImagesAndColorToCSSBackgroundLayerArray,
        cssBackgroundLayerArrayToBackgroundImagesAndColor,
        stylePropPathMappingFn,
      ),
    {
      wrapper: contextProvider,
    },
  )
  return result.current
}
Example #5
Source File: index.test.tsx    From firebase-tools-ui with Apache License 2.0 6 votes vote down vote up
describe('ExtensionsRedirect', () => {
  it('redirects to list page', () => {
    const wrapper: React.FC<React.PropsWithChildren<unknown>> = ({
      children,
    }) => {
      return (
        <MemoryRouter>
          <RedirectToList />
          {children}
        </MemoryRouter>
      );
    };

    const { result } = renderHook(() => useLocation(), { wrapper });
    expect(result.current.pathname).toBe('/extensions');
  });
});
Example #6
Source File: useExtensions.test.tsx    From firebase-tools-ui with Apache License 2.0 6 votes vote down vote up
describe('useExtensions', () => {
  it('returns the list of extension backends', () => {
    const wrapper: React.FC<React.PropsWithChildren<unknown>> = ({
      children,
    }) => (
      <ExtensionsProvider extensions={[EXTENSION]}>
        {children}
      </ExtensionsProvider>
    );

    const { result } = renderHook(() => useExtensions(), { wrapper });

    expect(result.current).toEqual([EXTENSION]);
  });
});
Example #7
Source File: useExtension.test.tsx    From firebase-tools-ui with Apache License 2.0 6 votes vote down vote up
describe('useExtension', () => {
  it('returns the unique extension backend with instance-id', () => {
    const want: Extension = convertBackendToExtension({
      env: {},
      extensionInstanceId: 'foo-published',
      extension: BACKEND_EXTENSION,
      extensionVersion: EXTENSION_VERSION,
      functionTriggers: [],
    });
    const other: Extension = convertBackendToExtension({
      env: {},
      extensionInstanceId: 'foo-local',
      extensionSpec: EXTENSION_SPEC,
      functionTriggers: [],
    });

    const wrapper: React.FC<React.PropsWithChildren<unknown>> = ({
      children,
    }) => (
      <ExtensionsProvider extensions={[want, other]}>
        <InstanceIdProvider instanceId="foo-published">
          {children}
        </InstanceIdProvider>
      </ExtensionsProvider>
    );

    const { result } = renderHook(() => useExtension(), { wrapper });
    expect(result.current).toEqual(want);
  });
});
Example #8
Source File: useFunctionsEmulator.test.tsx    From firebase-tools-ui with Apache License 2.0 6 votes vote down vote up
describe('useFunctionsEmulator', () => {
  it('returns the emulator URL', () => {
    const wrapper: React.FC<React.PropsWithChildren<unknown>> = ({
      children,
    }) => {
      return (
        <TestEmulatorConfigProvider
          config={{
            projectId: '',
            extensions: {
              hostAndPort,
              host: 'pirojok',
              port: 689,
            },
          }}
        >
          <Suspense fallback={null}>{children}</Suspense>
        </TestEmulatorConfigProvider>
      );
    };

    const { result } = renderHook(() => useFunctionsEmulator(), { wrapper });
    expect(result.current).toEqual(`http://${hostAndPort}`);
  });
});
Example #9
Source File: useExtensionBackends.test.tsx    From firebase-tools-ui with Apache License 2.0 6 votes vote down vote up
describe('useExtensionBackends', () => {
  it('returns the list of extension backends', async () => {
    mockExtensionBackends(BACKEND_LIST);
    const wrapper: React.FC<React.PropsWithChildren<unknown>> = ({
      children,
    }) => {
      return (
        <TestExtensionsProvider>
          <Suspense fallback={null}>{children}</Suspense>
        </TestExtensionsProvider>
      );
    };

    const { result } = renderHook(() => useExtensionBackends(), {
      wrapper,
    });

    await waitFor(() => result.current !== null);
    await waitFor(() => delay(100));

    expect(result.current).toEqual([BACKEND_LIST[0], BACKEND_LIST[1]]);
  });
});
Example #10
Source File: useSubscription.spec.tsx    From mqtt-react-hooks with MIT License 5 votes vote down vote up
describe('useSubscription', () => {
  beforeAll(() => {
    wrapper = ({ children }) => (
      <Connector brokerUrl={URL} options={options}>
        {children}
      </Connector>
    );
  });

  afterEach(cleanup)

  it('should get message on topic test', async () => {
    const { result } = renderHook(
      () => useSubscription(TOPIC),
      {
        wrapper,
      },
    );

    await waitFor(() => expect(result.current.client?.connected).toBe(true));

    const message = 'testing message';
    result.current.client?.publish(TOPIC, message, (err) => {
      expect(err).toBeFalsy()
    });

    await waitFor(() => expect(result.current?.message?.message).toBe(message));
  });

  it('should get message on topic with single selection of the path + ', async () => {
    const { result } = renderHook(
      () => useSubscription(`${TOPIC}/+/test/+/selection`),
      {
        wrapper,
      },
    );

    await waitFor(() => expect(result.current.client?.connected).toBe(true));

    const message = 'testing single selection message';

    result.current.client?.publish(
      `${TOPIC}/match/test/single/selection`,
      message,
    );

    await waitFor(() => expect(result.current.message?.message).toBe(message));
  });

  it('should get message on topic with # wildcard', async () => {
    const { result } = renderHook(
      () => useSubscription(`${TOPIC}/#`),
      {
        wrapper,
      },
    );

    await waitFor(() => expect(result.current.client?.connected).toBe(true));

    const message = 'testing with # wildcard';

    result.current.client?.publish(`${TOPIC}/match/test/wildcard`, message);

    await waitFor(() => expect(result.current.message?.message).toBe(message));
  });
});
Example #11
Source File: usePath.test.tsx    From firebase-tools-ui with Apache License 2.0 5 votes vote down vote up
describe('usePath', () => {
  function setup() {
    const history = createMemoryHistory({
      initialEntries: [`/storage/${bucket}/${initialPath}`],
    });

    const Wrapper: React.FC<React.PropsWithChildren<unknown>> = ({
      children,
    }) => {
      return (
        <Router history={history}>
          <Route exact path={storagePath + `:bucket/:path*`}>
            {children}
          </Route>
        </Router>
      );
    };
    const { result } = renderHook(() => usePath(), { wrapper: Wrapper });

    const [path, setPath] = result.current;
    return {
      path,
      setPath,
      history,
    };
  }

  it('picks up path from the URL', async () => {
    const { path } = setup();
    expect(path).toBe(initialPath);
  });

  it('updates the URL', async () => {
    const newPath = 'new/path';
    const { setPath, history } = setup();
    act(() => {
      setPath(newPath);
    });
    expect(history.location.pathname).toBe(`/storage/${bucket}/${newPath}`);
  });
});
Example #12
Source File: useBucket.test.tsx    From firebase-tools-ui with Apache License 2.0 5 votes vote down vote up
describe('useBucket', () => {
  function setup() {
    const history = createMemoryHistory({
      initialEntries: ['/storage/' + initialBucketName],
    });

    const Wrapper: React.FC<React.PropsWithChildren<unknown>> = ({
      children,
    }) => {
      return (
        <Router history={history}>
          <Route exact path={storagePath + `:bucket/:path*`}>
            {children}
          </Route>
        </Router>
      );
    };
    const { result } = renderHook(() => useBucket(), { wrapper: Wrapper });

    const [bucket, setBucket] = result.current;
    return {
      bucket,
      setBucket,
      history,
    };
  }

  it('picks up bucket name from the URL', async () => {
    const { bucket } = setup();
    expect(bucket).toBe(initialBucketName);
  });

  it('updates the URL', async () => {
    const newBucketName = 'lol';

    const { setBucket, history } = setup();
    act(() => {
      setBucket(newBucketName);
    });
    expect(history.location.pathname).toBe('/storage/' + newBucketName);
  });
});
Example #13
Source File: longhand-shorthand-hooks.spec.tsx    From utopia with MIT License 5 votes vote down vote up
function getPaddingHookResult<P extends ParsedPropertiesKeys, S extends ParsedPropertiesKeys>(
  longhands: Array<P>,
  shorthand: S,
  styleObjectExpressions: Array<string>,
  spiedProps: Array<any>,
  computedStyles: Array<ComputedStyle>,
  attributeMetadatas: Array<StyleAttributeMetadata>,
) {
  const props = styleObjectExpressions.map(
    (styleExpression) => getPropsForStyleProp(styleExpression, ['style'])!,
  )

  const mockDispatch = jest.fn()

  // eslint-disable-next-line @typescript-eslint/ban-types
  const contextProvider: React.FunctionComponent<React.PropsWithChildren<{}>> = ({ children }) => {
    const InspectorContextProvider = makeInspectorHookContextProvider(
      [TestSelectedComponent],
      props,
      ['style'],
      spiedProps,
      computedStyles,
      attributeMetadatas,
    )

    const initialEditorStore: EditorStorePatched = {
      editor: null as any,
      derived: null as any,
      strategyState: null as any,
      history: null as any,
      userState: null as any,
      workers: null as any,
      persistence: null as any,
      dispatch: mockDispatch,
      alreadySaved: false,
      builtInDependencies: [],
    }

    const storeHook = create<
      EditorStorePatched,
      SetState<EditorStorePatched>,
      GetState<EditorStorePatched>,
      Mutate<StoreApi<EditorStorePatched>, [['zustand/subscribeWithSelector', never]]>
    >(subscribeWithSelector(() => initialEditorStore))

    return (
      <EditorStateContext.Provider value={{ api: storeHook, useStore: storeHook }}>
        <InspectorContextProvider>{children}</InspectorContextProvider>
      </EditorStateContext.Provider>
    )
  }

  const { result } = renderHook(
    () =>
      useInspectorInfoLonghandShorthand(
        longhands,
        shorthand,
        stylePropPathMappingFn as any, // ¯\_(ツ)_/¯
      ),
    {
      wrapper: contextProvider,
    },
  )
  return { hookResult: result.current, mockDispatch: mockDispatch }
}
Example #14
Source File: Connector.spec.tsx    From mqtt-react-hooks with MIT License 5 votes vote down vote up
describe('Connector wrapper', () => {
  beforeAll(() => {
    wrapper = ({ children }) => (
      <Connector brokerUrl={URL} options={options}>
        {children}
      </Connector>
    );
  });

  it('should not connect with mqtt, wrong url', async () => {
    const { result } = renderHook(() => useMqttState(), {
      wrapper: ({ children }) => (
        <Connector
          brokerUrl="mqtt://test.mosqu.org:1884"
          options={{ connectTimeout: 2000 }}
        >
          {children}
        </Connector>
      ),
    });

    await waitFor(() => expect(result.current.connectionStatus).toBe('Offline'));
  });

  it('should connect with mqtt', async () => {
    const { result } = renderHook(() => useMqttState(), {
      wrapper,
    });

    await waitFor(() => expect(result.current.client?.connected).toBe(true));

    expect(result.current.connectionStatus).toBe('Connected');

    await act(async () => {
      result.current.client?.end();
    });
  });

  it('should connect passing props', async () => {
    const { result } = renderHook(() => useMqttState(), {
      wrapper: ({ children }) => (
        <Connector
          brokerUrl={URL}
          options={{ clientId: 'testingMqttUsingProps' }}
        >
          {children}
        </Connector>
      ),
    });

    await waitFor(() => expect(result.current.client?.connected).toBe(true));

    expect(result.current.connectionStatus).toBe('Connected');

    await act(async () => {
      result.current.client?.end();
    });
  });
});
Example #15
Source File: useExtensionsData.test.tsx    From firebase-tools-ui with Apache License 2.0 4 votes vote down vote up
describe('useExtensionsData', () => {
  it('returns the list of extension row extensions', async () => {
    mockExtensionBackends(BACKEND_LIST);
    const wrapper: React.FC<React.PropsWithChildren<unknown>> = ({
      children,
    }) => {
      return (
        <TestEmulatorConfigProvider config={CONFIG_WITH_EXTENSION}>
          <Suspense fallback={null}>{children}</Suspense>
        </TestEmulatorConfigProvider>
      );
    };

    const { result } = renderHook(() => useExtensionsData(), {
      wrapper,
    });

    await waitFor(() => delay(100));

    expect(result.current).toEqual([
      {
        authorName: 'Awesome Inc',
        authorUrl: 'https://google.com/awesome',
        params: [],
        name: 'good-tool',
        displayName: 'Pirojok-the-tool',
        specVersion: 'v1beta',
        env: {},
        apis: [
          {
            apiName: 'storage-component.googleapis.com',
            reason: 'Needed to use Cloud Storage',
          },
        ],
        resources: [
          {
            type: 'firebaseextensions.v1beta.function',
            description:
              'Listens for new images uploaded to your specified Cloud Storage bucket, resizes the images, then stores the resized images in the same bucket. Optionally keeps or deletes the original images.',
            name: 'generateResizedImage',
            propertiesYaml:
              'availableMemoryMb: 1024\neventTrigger:\n  eventType: google.storage.object.finalize\n  resource: projects/_/buckets/${param:IMG_BUCKET}\nlocation: ${param:LOCATION}\nruntime: nodejs14\n',
          },
        ],
        roles: [
          {
            role: 'storage.admin',
            reason:
              'Allows the extension to store resized images in Cloud Storage',
          },
        ],
        readmeContent: '',
        postinstallContent: '### See it in action',
        sourceUrl: '',
        extensionDetailsUrl:
          'https://firebase.google.com/products/extensions/good-tool',
        id: 'pirojok-the-published-extension',
        ref: 'awesome-inc/[email protected]',
        iconUri:
          'https://www.gstatic.com/mobilesdk/211001_mobilesdk/google-pay-logo.svg',
        publisherIconUri:
          'https://www.gstatic.com/mobilesdk/160503_mobilesdk/logo/2x/firebase_128dp.png',
      },
      {
        authorName: 'Awesome Inc',
        authorUrl: 'https://google.com/awesome',
        params: [],
        name: 'good-tool',
        displayName: 'Good Tool',
        specVersion: 'v1beta',
        env: {},
        apis: [
          {
            apiName: 'storage-component.googleapis.com',
            reason: 'Needed to use Cloud Storage',
          },
        ],
        resources: [
          {
            type: 'firebaseextensions.v1beta.function',
            description:
              'Listens for new images uploaded to your specified Cloud Storage bucket, resizes the images, then stores the resized images in the same bucket. Optionally keeps or deletes the original images.',
            name: 'generateResizedImage',
            propertiesYaml:
              'availableMemoryMb: 1024\neventTrigger:\n  eventType: google.storage.object.finalize\n  resource: projects/_/buckets/${param:IMG_BUCKET}\nlocation: ${param:LOCATION}\nruntime: nodejs14\n',
          },
        ],
        roles: [
          {
            role: 'storage.admin',
            reason:
              'Allows the extension to store resized images in Cloud Storage',
          },
        ],
        readmeContent: '',
        postinstallContent: '### See it in action',
        sourceUrl: '',
        extensionDetailsUrl:
          'https://firebase.google.com/products/extensions/good-tool',
        id: 'pirojok-the-local-extension',
      },
    ]);
  });
});
Example #16
Source File: property-path-hooks.spec.tsx    From utopia with MIT License 4 votes vote down vote up
describe('useGetOrderedPropertyKeys', () => {
  function getPaddingHookResult<P extends ParsedCSSPropertiesKeys>(
    propsKeys: Array<P>,
    styleObjectExpressions: Array<string>,
    spiedProps: Array<any>,
    computedStyles: Array<ComputedStyle>,
    attributeMetadatas: Array<StyleAttributeMetadata>,
  ) {
    const props = styleObjectExpressions.map(
      (styleExpression) => getPropsForStyleProp(styleExpression, ['style'])!,
    )

    const contextProvider = makeInspectorHookContextProvider(
      [],
      props,
      ['style'],
      spiedProps,
      computedStyles,
      attributeMetadatas,
    )

    const { result } = renderHook(
      () => useGetOrderedPropertyKeys<P>(stylePropPathMappingFn, propsKeys),
      {
        wrapper: contextProvider,
      },
    )
    return result.current
  }

  it('does not contain entry for nonexistent prop 1', () => {
    const hookResult = getPaddingHookResult(
      ['paddingLeft', 'padding'],
      [`{ paddingLeft: 5 }`],
      [{ paddingLeft: 5 }],
      [{ paddingTop: '0px', paddingRight: '0px', paddingBottom: '0px', paddingLeft: '15px' }],
      [],
    )
    expect(hookResult).toEqual([['paddingLeft']])
  })

  it('does not contain entry for nonexistent prop 2', () => {
    const hookResult = getPaddingHookResult(
      ['paddingLeft', 'padding'],
      [`{ padding: 15 }`],
      [{ padding: 15 }],
      [{ paddingTop: '15px', paddingRight: '15px', paddingBottom: '15px', paddingLeft: '15px' }],
      [],
    )
    expect(hookResult).toEqual([['padding']])
  })

  it('does contain entry for prop explicitly set to undefined', () => {
    const hookResult = getPaddingHookResult(
      ['paddingLeft', 'padding'],
      [`{ padding: 15, paddingLeft: undefined }`],
      [{ padding: 15, paddingLeft: undefined }],
      [{ paddingTop: '15px', paddingRight: '15px', paddingBottom: '15px', paddingLeft: '15px' }],
      [],
    )
    expect(hookResult).toEqual([['padding', 'paddingLeft']])
  })

  it('keeps the order of props for single select 1', () => {
    const hookResult = getPaddingHookResult(
      ['paddingLeft', 'padding'],
      [`{ paddingLeft: 5, padding: 15 }`],
      [{ paddingLeft: 5, padding: 15 }],
      [{ paddingTop: '15px', paddingRight: '15px', paddingBottom: '15px', paddingLeft: '15px' }],
      [],
    )
    expect(hookResult).toEqual([['paddingLeft', 'padding']])
  })

  it('keeps the order of props for single select 2', () => {
    const hookResult = getPaddingHookResult(
      ['paddingLeft', 'padding'],
      [`{ padding: 15, paddingLeft: 5 }`],
      [{ padding: 15, paddingLeft: 5 }],
      [{ paddingTop: '15px', paddingRight: '15px', paddingBottom: '15px', paddingLeft: '5px' }],
      [],
    )
    expect(hookResult).toEqual([['padding', 'paddingLeft']])
  })

  it('works with controlled longhand', () => {
    const hookResult = getPaddingHookResult(
      ['paddingLeft', 'padding'],
      [`{ paddingLeft: 5 + 5 }`],
      [{ paddingLeft: 10 }],
      [{ paddingLeft: '10px' }],
      [],
    )
    expect(hookResult).toEqual([['paddingLeft']])
  })

  it('keeps the order of props for multi select 1', () => {
    const hookResult = getPaddingHookResult(
      ['paddingLeft', 'padding'],
      [`{ padding: 15 }`, `{ padding: 15 }`],
      [{ padding: 15 }, { padding: 15 }],
      [
        { paddingTop: '15px', paddingRight: '15px', paddingBottom: '15px', paddingLeft: '15px' },
        { paddingTop: '15px', paddingRight: '15px', paddingBottom: '15px', paddingLeft: '15px' },
      ],
      [],
    )
    expect(hookResult).toEqual([['padding'], ['padding']])
  })

  it('keeps the order of props for multi select 2', () => {
    const hookResult = getPaddingHookResult(
      ['paddingLeft', 'padding'],
      [`{ paddingLeft: 5, padding: 15 }`, `{ paddingLeft: 5, padding: 15 }`],
      [
        { paddingLeft: 5, padding: 15 },
        { paddingLeft: 5, padding: 15 },
      ],
      [
        { paddingTop: '15px', paddingRight: '15px', paddingBottom: '15px', paddingLeft: '15px' },
        { paddingTop: '15px', paddingRight: '15px', paddingBottom: '15px', paddingLeft: '15px' },
      ],
      [],
    )

    expect(hookResult).toEqual([
      ['paddingLeft', 'padding'],
      ['paddingLeft', 'padding'],
    ])
  })

  it('multiselect: the paddings are in different order 1', () => {
    const hookResult = getPaddingHookResult(
      ['paddingLeft', 'padding'],
      [`{ paddingLeft: 5, padding: 15 }`, `{ padding: 15 }`],
      [{ paddingLeft: 5, padding: 15 }, { padding: 15 }],
      [
        { paddingTop: '15px', paddingRight: '15px', paddingBottom: '15px', paddingLeft: '15px' },
        { paddingTop: '15px', paddingRight: '15px', paddingBottom: '15px', paddingLeft: '15px' },
      ],
      [],
    )

    expect(hookResult).toEqual([['paddingLeft', 'padding'], ['padding']])
  })

  it('multiselect: the paddings are in different order 2', () => {
    const hookResult = getPaddingHookResult(
      ['paddingLeft', 'padding'],
      [`{ padding: 15, paddingLeft: 5 }`, `{ padding: 15 }`],
      [{ padding: 15, paddingLeft: 5 }, { padding: 15 }],
      [
        { paddingTop: '15px', paddingRight: '15px', paddingBottom: '15px', paddingLeft: '5px' },
        { paddingTop: '15px', paddingRight: '15px', paddingBottom: '15px', paddingLeft: '15px' },
      ],
      [],
    )
    expect(hookResult).toEqual([['padding', 'paddingLeft'], ['padding']])
  })
})
Example #17
Source File: property-path-hooks.spec.tsx    From utopia with MIT License 4 votes vote down vote up
describe('Integration Test: boxShadow property', () => {
  function getBoxShadowHookResult(
    boxShadowExpressions: Array<string>,
    spiedProps: Array<any>,
    computedStyles: Array<ComputedStyle>,
    attributeMetadatas: Array<StyleAttributeMetadata>,
  ) {
    const props = boxShadowExpressions.map(
      (boxShadow) => getPropsForStyleProp(boxShadow, ['myStyleOuter', 'myStyleInner'])!,
    )

    const contextProvider = makeInspectorHookContextProvider(
      [],
      props,
      ['myStyleOuter', 'myStyleInner'],
      spiedProps,
      computedStyles,
      attributeMetadatas,
    )

    const { result } = renderHook(() => useInspectorStyleInfo('boxShadow'), {
      wrapper: contextProvider,
    })
    return result.current
  }

  it('poorly formed shows up as unknown', () => {
    const hookResult = getBoxShadowHookResult(
      [`{ boxShadow: '1px 1px burple' }`],
      [{ boxShadow: '1px 1px burple' }],
      [{ boxShadow: '1px 1px burple' }],
      [],
    )
    const expectedControlStatus: ControlStatus = 'simple-unknown-css'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })

  it('multiselected poorly formed shows up as unknown', () => {
    const hookResult = getBoxShadowHookResult(
      [
        `{ boxShadow: '1px 1px burple' }`,
        `{ boxShadow: '1px 1px purple' }`,
        `{ boxShadow: '1px 1px beeple' }`,
        `{ boxShadow: '1px 1px boople' }`,
      ],
      [
        { boxShadow: '1px 1px burple' },
        { boxShadow: '1px 1px purple' },
        { boxShadow: '1px 1px beeple' },
        { boxShadow: '1px 1px boople' },
      ],
      [
        { boxShadow: '1px 1px burple' },
        { boxShadow: '1px 1px purple' },
        { boxShadow: '1px 1px beeple' },
        { boxShadow: '1px 1px boople' },
      ],
      [],
    )
    const expectedControlStatus: ControlStatus = 'multiselect-simple-unknown-css'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })

  it('shows up as simple', () => {
    const hookResult = getBoxShadowHookResult(
      [`{ boxShadow: '0 0 0 1px #ff00ff' }`],
      [{ boxShadow: '0 0 0 1px #ff00ff' }],
      [{ boxShadow: '0 0 0 1px #ff00ff' }],
      [],
    )
    const expectedControlStatus: ControlStatus = 'simple'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })

  it('with a controlled parameter shows up as controlled', () => {
    const hookResult = getBoxShadowHookResult(
      [`{ boxShadow: 5 + 15 }`],
      [{ boxShadow: '20' }],
      [{ boxShadow: '20' }],
      [],
    )
    const expectedControlStatus: ControlStatus = 'controlled'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })

  it('multiselect, with a controlled parameter shows up as controlled', () => {
    const hookResult = getBoxShadowHookResult(
      [`{ boxShadow: 5 + 15 }`, `{ boxShadow: 5 + 15 }`],
      [{ boxShadow: '20' }, { boxShadow: '20' }],
      [{ boxShadow: '20' }, { boxShadow: '20' }],
      [],
    )
    const expectedControlStatus: ControlStatus = 'multiselect-controlled'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })

  it('multiselect with a mixed value, with a controlled parameter shows up as controlled', () => {
    const hookResult = getBoxShadowHookResult(
      [`{ boxShadow: 5 + 15 }`, `{ boxShadow: 5 + 25 }`],
      [{ boxShadow: '20' }, { boxShadow: '30' }],
      [{ boxShadow: '20' }, { boxShadow: '30' }],
      [],
    )
    const expectedControlStatus: ControlStatus = 'multiselect-controlled'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })
})
Example #18
Source File: property-path-hooks.spec.tsx    From utopia with MIT License 4 votes vote down vote up
describe('Integration Test: opacity property', () => {
  function getOpacityHookResult(
    opacityExpressions: Array<string>,
    spiedProps: Array<any>,
    computedStyles: Array<ComputedStyle>,
    attributeMetadatas: Array<StyleAttributeMetadata>,
  ) {
    const propses = opacityExpressions.map(
      (expression) => getPropsForStyleProp(expression, ['myStyleOuter', 'myStyleInner'])!,
    )

    const contextProvider = ({ children }: any) => (
      <InspectorPropsContext.Provider
        value={{
          selectedViews: [],
          editedMultiSelectedProps: propses,
          targetPath: ['myStyleOuter', 'myStyleInner'],
          spiedProps: spiedProps,
          computedStyles: computedStyles,
          selectedAttributeMetadatas: attributeMetadatas,
        }}
      >
        {children}
      </InspectorPropsContext.Provider>
    )

    const { result } = renderHook(() => useInspectorStyleInfo('opacity'), {
      wrapper: contextProvider,
    })
    return result.current
  }

  it('parses a off control status', () => {
    const hookResult = getOpacityHookResult([], [], [], [])

    const expectedControlStatus: ControlStatus = 'unset'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })

  it('parses an unset control status', () => {
    const hookResult = getOpacityHookResult([`{}`], [{}], [], [])
    const expectedControlStatus: ControlStatus = 'unset'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })

  it('parses a multiselect-identical-unset control status', () => {
    const hookResult = getOpacityHookResult([`{}`, `{}`], [{}, {}], [], [])

    const expectedControlStatus: ControlStatus = 'multiselect-identical-unset'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })

  it('parses a simple control status', () => {
    const expectedValue = cssNumber(0.9)

    const hookResult = getOpacityHookResult(
      [`{opacity: 0.9}`],
      [{ opacity: 0.9 }],
      [{ opacity: '0.9' }],
      [],
    )

    expect(hookResult.value).toEqual(expectedValue)

    const expectedControlStatus: ControlStatus = 'simple'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })

  it('parses a simple-unknown-css control status', () => {
    const hookResult = getOpacityHookResult(
      [`{opacity: 'a garbage'}`],
      [{ opacity: 'a garbage' }],
      [{ opacity: 'a garbage' }],
      [],
    )

    const expectedControlStatus: ControlStatus = 'simple-unknown-css'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })

  it('parses a multiselect-identical-simple control status', () => {
    const expectedValue = cssNumber(0.9)

    const hookResult = getOpacityHookResult(
      [`{opacity: 0.9}`, `{opacity: 0.9}`],
      [{ opacity: 0.9 }, { opacity: 0.9 }],
      [{ opacity: '0.9' }, { opacity: '0.9' }],
      [],
    )

    expect(hookResult.value).toEqual(expectedValue)

    const expectedControlStatus: ControlStatus = 'multiselect-identical-simple'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })

  it('parses a multiselect-simple-unknown-css control status', () => {
    const hookResults = [
      getOpacityHookResult(
        [`{opacity: 'a garbage'}`, `{opacity: 0.9}`],
        [{ opacity: 'a garbage' }, { opacity: 0.9 }],
        [{ opacity: 'a garbage' }, { opacity: '0.9' }],
        [],
      ),
      getOpacityHookResult(
        [`{opacity: 0.9}`, `{opacity: 'a garbage'}`],
        [{ opacity: 0.9 }, { opacity: 'a garbage' }],
        [{ opacity: '0.9' }, { opacity: 'a garbage' }],
        [],
      ),
      getOpacityHookResult(
        [`{opacity: 1}`, `{opacity: 0.9}`, `{opacity: 'a garbage'}`],
        [{ opacity: 1 }, { opacity: 0.9 }, { opacity: 'a garbage' }],
        [{ opacity: '1' }, { opacity: '0.9' }, { opacity: 'a garbage' }],
        [],
      ),
    ]

    const expectedControlStatus: ControlStatus = 'multiselect-simple-unknown-css'
    hookResults.forEach((hookResult) => {
      expect(hookResult.controlStatus).toEqual(expectedControlStatus)
    })
  })

  it('parses a multiselect-mixed-simple-or-unset control status', () => {
    const expectedValue = cssNumber(0.9)

    const hookResult = getOpacityHookResult(
      [`{opacity: 0.9}`, `{opacity: 0.5}`],
      [{ opacity: 0.9 }, { opacity: 0.5 }],
      [{ opacity: '0.9' }, { opacity: '0.5' }],
      [],
    )

    expect(hookResult.value).toEqual(expectedValue)

    const expectedControlStatus: ControlStatus = 'multiselect-mixed-simple-or-unset'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })

  it('parses a controlled control status', () => {
    const hookResult = getOpacityHookResult(
      [`{opacity: true ? 1 : 0.1}`],
      [{ opacity: 1 }],
      [{ opacity: '1' }],
      [],
    )
    const expectedControlStatus: ControlStatus = 'controlled'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })

  it('parses a multiselect-controlled control status', () => {
    const hookResult = getOpacityHookResult(
      [`{opacity: true ? 1 : 0.1}`, `{opacity: true ? 1 : 0.1}`],
      [{ opacity: 1 }, { opacity: 1 }],
      [{ opacity: '1' }, { opacity: '1' }],
      [],
    )
    const expectedControlStatus: ControlStatus = 'multiselect-controlled'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })

  xit('parses an unoverwritable control status', () => {
    const hookResult = getOpacityHookResult([`nodeValue1`], [`nodeValue1`], [], [])
    const expectedControlStatus: ControlStatus = 'unoverwritable'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })

  xit('parses a multiselect-unoverwritable control status', () => {
    const hookResult = getOpacityHookResult(
      [`nodeValue1`, `nodeValue1`],
      [`nodeValue1`, `nodeValue1`],
      [],
      [],
    )
    const expectedControlStatus: ControlStatus = 'multiselect-unoverwritable'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })

  xit('parses a multiselect-unoverwritable control status 2', () => {
    const hookResult = getOpacityHookResult(
      [`nodeValue1`, `nodeValue2`],
      [`nodeValue1`, `nodeValue2`],
      [],
      [],
    )
    const expectedControlStatus: ControlStatus = 'multiselect-unoverwritable'
    expect(hookResult.controlStatus).toEqual(expectedControlStatus)
  })
})
Example #19
Source File: property-path-hooks.spec.tsx    From utopia with MIT License 4 votes vote down vote up
describe('useCallbackFactory', () => {
  it('the returned factory memoizes based on inputs', () => {
    const oldValue = 'a value'
    const aCallback = () => {}

    const aTransform = () => 'hello'

    const { result, rerender } = renderHook((props) => useRenderTestHook(props), {
      initialProps: {
        value: oldValue,
        callback: aCallback,
        transformFunction1: aTransform,
      },
    })

    const { submitValue1 } = result.current

    rerender({
      value: oldValue,
      callback: aCallback,
      transformFunction1: aTransform,
    })

    const { submitValue1: submitValue1b } = result.current

    expect(submitValue1).toStrictEqual(submitValue1b)
  })
  it('the returned factory memoizes based on inputs even if the factory is used more than once', () => {
    const oldValue = 'a value'
    const aCallback = () => {}

    const aTransform = () => 'hello'
    const bTransform = () => 'ello'

    const { result, rerender } = renderHook((props) => useRenderTestHook(props), {
      initialProps: {
        value: oldValue,
        callback: aCallback,
        transformFunction1: aTransform,
        transformFunction2: bTransform,
      },
    })

    const { submitValue1, submitValue2 } = result.current

    rerender({
      value: oldValue,
      callback: aCallback,
      transformFunction1: aTransform,
      transformFunction2: bTransform,
    })

    const { submitValue1: submitValue1b, submitValue2: submitValue2b } = result.current

    expect(submitValue1).toStrictEqual(submitValue1b)
    expect(submitValue2).toStrictEqual(submitValue2b!)
  })

  it('the returned factory returns new callbacks if the oldValue or the main callback changes', () => {
    const oldValue = 'a value'
    const newValue = 'b value'
    const aCallback = () => {}

    const aTransform = () => 'hello'
    const bTransform = () => 'ello'

    const { result, rerender } = renderHook((props) => useRenderTestHook(props), {
      initialProps: {
        value: oldValue,
        callback: aCallback,
        transformFunction1: aTransform,
        transformFunction2: bTransform,
      },
    })

    const { submitValue1, submitValue2 } = result.current

    rerender({
      value: newValue,
      callback: aCallback,
      transformFunction1: aTransform,
      transformFunction2: bTransform,
    })

    const { submitValue1: submitValue1b, submitValue2: submitValue2b } = result.current

    expect(submitValue1).not.toStrictEqual(submitValue1b)
    expect(submitValue2).not.toStrictEqual(submitValue2b!)
  })
})
Example #20
Source File: property-controls-hooks.spec.tsx    From utopia with MIT License 4 votes vote down vote up
function callPropertyControlsHook(selectedViews: ElementPath[]) {
  const persistentModel = createTestProjectWithCode(`
  import * as React from 'react'
  import {
    View,
    Scene,
    Storyboard,
  } from 'utopia-api'
  export var App = (props) => {
    const propToDetect = props.testDetectedPropertyWithNoValue
    return (
      <div data-uid={'aaa'}/>
    )
  }
  // Note: for this test, we are not running the property controls parser. Otherwise this is where App.propertyControls would come

  export var OtherComponent = (props) => {
    return (
      <div data-uid={'aaa'}/>
    )
  }
  // Note: for this test, we are not running the property controls parser. Otherwise this is where App.propertyControls would come
 

  export var storyboard = (props) => {
    return (
      <Storyboard data-uid={'${BakedInStoryboardUID}'}>
        <Scene
          style={{ position: 'relative', left: 0, top: 0, width: 375, height: 812 }}
          data-uid={'${TestSceneUID}'}
        >
          <App
            data-uid='${TestAppUID}' 
            style={{ position: 'absolute', bottom: 0, left: 0, right: 0, top: 0 }}
            testPropWithoutControl='yes'
          />
          <App
            data-uid='${TestAppUID2}' 
            style={{ position: 'absolute', bottom: 0, left: 0, right: 0, top: 0 }}
            propWithControlButNoValue='but there is a value!'
          />
          <OtherComponent
            data-uid='${TestOtherComponentUID}' 
            style={{ position: 'absolute', bottom: 0, left: 0, right: 0, top: 0 }}
            propWithOtherKey={10}
          />
        </Scene>
      </Storyboard>
    )
  }
  `)

  // We manually have to create these ElementInstanceMetadatas because we are not running the canvas/spy/dom-walker for this test
  let metadata: ElementInstanceMetadataMap = {
    [EP.toString(selectedViews[0])]: elementInstanceMetadata(
      selectedViews[0],
      null as any,
      null,
      null,
      true,
      false,
      null as any,
      null,
      null,
      null,
      null,
    ),
  }
  let allElementProps: AllElementProps = {
    [EP.toString(selectedViews[0])]: { testPropWithoutControl: 'yes' },
  }

  if (selectedViews.length > 1) {
    metadata[EP.toString(selectedViews[1])] = elementInstanceMetadata(
      selectedViews[1],
      null as any,
      null,
      null,
      true,
      false,
      null as any,
      null,
      null,
      null,
      null,
    )
    allElementProps[EP.toString(selectedViews[1])] = {
      propWithControlButNoValue: 'but there is a value!',
    }
  }
  if (selectedViews.length > 2) {
    metadata[EP.toString(selectedViews[2])] = elementInstanceMetadata(
      selectedViews[2],
      null as any,
      null,
      null,
      true,
      false,
      null as any,
      null,
      null,
      null,
      null,
    )

    allElementProps[EP.toString(selectedViews[2])] = { propWithOtherKey: 10 }
  }

  const initialEditorState = editorModelFromPersistentModel(persistentModel, NO_OP)
  const editorState: EditorState = {
    ...initialEditorState,
    selectedViews: selectedViews,
    propertyControlsInfo: {
      ...initialEditorState.propertyControlsInfo,
      '/utopia/storyboard': {
        App: {
          properties: propertyControlsForApp,
          variants: [
            {
              insertMenuLabel: 'App',
              importsToAdd: {},
              elementToInsert: jsxElementWithoutUID('App', [], []),
            },
          ],
        },
        OtherComponent: {
          properties: propertyControlsForOtherComponent,
          variants: [
            {
              insertMenuLabel: 'OtherComponent',
              importsToAdd: {},
              elementToInsert: jsxElementWithoutUID('OtherComponent', [], []),
            },
          ],
        },
      },
    },
    jsxMetadata: metadata,
    allElementProps: allElementProps,
  }

  const initialEditorStore: EditorStorePatched = {
    editor: editorState,
    derived: null as any,
    strategyState: null as any,
    history: null as any,
    userState: null as any,
    workers: null as any,
    persistence: null as any,
    dispatch: null as any,
    alreadySaved: null as any,
    builtInDependencies: [],
  }

  const storeHook = create<
    EditorStorePatched,
    SetState<EditorStorePatched>,
    GetState<EditorStorePatched>,
    Mutate<StoreApi<EditorStorePatched>, [['zustand/subscribeWithSelector', never]]>
  >(subscribeWithSelector((set) => initialEditorStore))

  const inspectorCallbackContext: InspectorCallbackContextData = {
    selectedViewsRef: { current: selectedViews },
    onSubmitValue: null as any,
    onUnsetValue: null as any,
  }

  const contextProvider = ({ children }: any) => (
    <EditorStateContext.Provider value={{ useStore: storeHook, api: storeHook }}>
      <InspectorCallbackContext.Provider value={inspectorCallbackContext}>
        {children}
      </InspectorCallbackContext.Provider>
    </EditorStateContext.Provider>
  )

  const { result } = renderHook(() => useGetPropertyControlsForSelectedComponents(), {
    wrapper: contextProvider,
  })
  return { result: result.current, getEditorState: () => storeHook.getState() }
}
Example #21
Source File: store-hook.spec.tsx    From utopia with MIT License 4 votes vote down vote up
describe('useSelectorWithCallback', () => {
  it('The callback is not fired on first call', () => {
    const storeHook = createEmptyEditorStoreHook()

    let hookRenders = 0
    let callCount = 0

    const { result } = renderHook<void, { storeHook: UseStore<EditorStorePatched> }>(
      (props) => {
        hookRenders++
        return useSelectorWithCallback(
          (store) => store.editor.selectedViews,
          (newSelectedViews) => {
            callCount++
          },
        )
      },
      {
        wrapper: ContextProvider(storeHook),
        initialProps: {
          storeHook: storeHook,
        },
      },
    )

    expect(hookRenders).toEqual(1)
    expect(callCount).toEqual(0)
  })

  it('The callback is fired if the store changes', () => {
    const storeHook = createEmptyEditorStoreHook()

    let hookRenders = 0
    let callCount = 0

    const { result } = renderHook<void, { storeHook: UseStore<EditorStorePatched> }>(
      (props) => {
        hookRenders++
        return useSelectorWithCallback(
          (store) => store.editor.selectedViews,
          (newSelectedViews) => {
            callCount++
          },
        )
      },
      {
        wrapper: ContextProvider(storeHook),
        initialProps: {
          storeHook: storeHook,
        },
      },
    )

    storeHook.setState({
      editor: { selectedViews: [EP.fromString('sb/scene:aaa')] } as EditorState,
    })

    expect(hookRenders).toEqual(1)
    expect(callCount).toEqual(1)
  })

  it('The callback is fired if the hook is rerendered in a race condition and happens earlier than the zustand subscriber could be notified', () => {
    const storeHook = createEmptyEditorStoreHook()

    let hookRenders = 0
    let callCount = 0
    let hookRendersWhenCallbackWasFired = -1
    let callCountWhenCallbackWasFired = -1

    let rerenderTestHook: () => void

    storeHook.subscribe(
      (store) => store.editor.selectedViews,
      (newSelectedViews) => {
        if (newSelectedViews != null) {
          rerenderTestHook()
          callCountWhenCallbackWasFired = callCount
          hookRendersWhenCallbackWasFired = hookRenders
          // TODO this is super-baffling. turning this test async and putting done() here and expect()-ing values did not work for some reason
        }
      },
    )

    const { result, rerender } = renderHook<void, { storeHook: UseStore<EditorStorePatched> }>(
      (props) => {
        hookRenders++
        return useSelectorWithCallback(
          (store) => store.editor.selectedViews,
          (newSelectedViews) => {
            callCount++
          },
        )
      },
      {
        wrapper: ContextProvider(storeHook),
        initialProps: {
          storeHook: storeHook,
        },
      },
    )

    rerenderTestHook = () => {
      rerender({ storeHook: storeHook })
    }

    storeHook.setState({
      editor: { selectedViews: [EP.fromString('sb/scene:aaa')] } as EditorState,
    })

    storeHook.destroy()

    expect(hookRenders).toEqual(2)
    expect(hookRendersWhenCallbackWasFired).toEqual(2)
    expect(callCount).toEqual(1)
    expect(callCountWhenCallbackWasFired).toEqual(1)
  })
})
Example #22
Source File: useStorageFiles.test.tsx    From firebase-tools-ui with Apache License 2.0 4 votes vote down vote up
describe('useStorageFiles', () => {
  async function setup() {
    mockBuckets(['bucket']);

    const storage = renderHook(() => useStorageFiles(), {
      wrapper: FakeStorageWrappers,
    });

    await waitFor(() => expect(storage.result.current).not.toBeNull());

    await act(async () => {
      await storage.result.current.deleteAllFiles();
    });

    return {
      uploadFile,
      storage,
      waitForNFiles,
      deleteFiles,
    };

    async function waitForNFiles(n: number) {
      await waitFor(() => {
        expect(storage.result.current.files!.length).toBe(n);
      });
      return true;
    }

    async function uploadFile(fileName: string, folderName?: string) {
      const file = new File([''], fileName);

      await act(async () => {
        await storage.result.current.uploadFiles([file], folderName);
      });
    }

    async function deleteFiles(files: string[]) {
      await act(async () => {
        await storage.result.current.deleteFiles(files);
      });
    }
  }

  const fileName = 'lol.txt';
  const folderName = 'folder';

  it('uploads files', async () => {
    const { uploadFile, waitForNFiles, storage } = await setup();
    await uploadFile(fileName);
    await uploadFile('file.txt', folderName);

    await waitForNFiles(2);

    expect(storage.result.current.files[0]).toEqual(
      expect.objectContaining({
        type: 'folder',
        name: folderName,
        fullPath: folderName,
      })
    );

    expect(storage.result.current.files[1]).toEqual(
      expect.objectContaining({
        type: 'file',
        name: fileName,
        fullPath: fileName,
      })
    );
  });

  it('deletes all files', async () => {
    const { storage, uploadFile, waitForNFiles } = await setup();

    await uploadFile(fileName);
    await uploadFile(fileName, folderName);

    await act(async () => {
      await storage.result.current.deleteAllFiles();
    });

    expect(await waitForNFiles(0)).toBe(true);
  });

  it('deletes specific files', async () => {
    const { uploadFile, storage, waitForNFiles, deleteFiles } = await setup();

    await uploadFile(fileName);
    await uploadFile('keep.me');
    await uploadFile('other.file', folderName);

    await act(async () => {
      await deleteFiles([folderName, fileName]);
    });

    await waitForNFiles(1);
    expect(storage.result.current.files!.length).toBe(1);
  }, 5000);
});
Example #23
Source File: useMultiselect.test.ts    From firebase-tools-ui with Apache License 2.0 4 votes vote down vote up
describe('useMultiSelect', () => {
  function setup() {
    const items = ['pirojok', 'pelmeni', 'kompot', 'chiken', 'borscht'];

    const { result } = renderHook(() => useMultiselect(items));

    function toggleSingle(item: string) {
      act(() => {
        result.current.toggleSingle(item);
      });
    }

    function toggleContinuous(item: string) {
      act(() => {
        result.current.toggleContinuous(item);
      });
    }

    function toggleAll() {
      act(() => {
        result.current.toggleAll();
      });
    }

    function clearAll() {
      act(() => {
        result.current.clearAll();
      });
    }

    return {
      result,
      items,
      toggleSingle,
      toggleContinuous,
      toggleAll,
      clearAll,
    };
  }

  describe('single selections', () => {
    it('has empty selection initially', async () => {
      const r = setup();
      expect(r.result.current.selected).toEqual(new Set());
    });

    it('allows to select single element', async () => {
      const { result, toggleSingle, items } = setup();

      toggleSingle(items[0]);

      expect(result.current.selected).toEqual(new Set([items[0]]));
    });

    it('allows to deselect single element', async () => {
      const { result, toggleSingle, items } = setup();

      toggleSingle(items[0]);
      toggleSingle(items[0]);

      expect(result.current.selected).toEqual(new Set([]));
    });

    it('allows to reselect single element', async () => {
      const { result, toggleSingle, items } = setup();

      toggleSingle(items[0]);
      toggleSingle(items[1]);
      toggleSingle(items[2]);
      toggleSingle(items[1]);

      expect(result.current.selected).toEqual(new Set([items[0], items[2]]));
    });
  });

  describe('selecting with shift', () => {
    it('allows to select multiple elements', async () => {
      const { result, toggleSingle, toggleContinuous, items } = setup();

      toggleSingle(items[0]);
      toggleContinuous(items[2]);

      expect(result.current.selected).toEqual(
        new Set([items[0], items[1], items[2]])
      );
    });

    it('allows to deselect multiple elements', async () => {
      const { result, toggleSingle, toggleContinuous, items } = setup();

      toggleSingle(items[0]);
      toggleSingle(items[1]);
      toggleSingle(items[2]);
      toggleContinuous(items[0]);

      expect(result.current.selected).toEqual(new Set([]));
    });

    it('allows multiple selections and reselections', async () => {
      const { result, toggleSingle, toggleContinuous, items } = setup();

      toggleSingle(items[0]);
      toggleContinuous(items[4]);

      expect(result.current.selected).toEqual(new Set(items));

      toggleContinuous(items[1]);

      expect(result.current.selected).toEqual(new Set([items[0]]));

      toggleContinuous(items[3]);

      expect(result.current.selected).toEqual(
        new Set([items[0], items[1], items[2], items[3]])
      );
    });

    it('allows for selections in different directions', async () => {
      const { result, toggleSingle, toggleContinuous, items } = setup();

      toggleSingle(items[2]);
      toggleContinuous(items[4]);

      expect(result.current.selected).toEqual(
        new Set([items[2], items[3], items[4]])
      );

      toggleContinuous(items[0]);

      expect(result.current.selected).toEqual(new Set(items));
    });

    it('when handling gap selections, sets the value to the latest checked item', async () => {
      const { result, toggleSingle, toggleContinuous, items } = setup();

      toggleSingle(items[0]);
      toggleSingle(items[2]);
      toggleSingle(items[4]);
      toggleContinuous(items[2]);

      expect(result.current.selected).toEqual(new Set([items[0]]));
    });
  });

  describe('toggleAll', () => {
    it('when nothing selected, selects everything', async () => {
      const { result, toggleAll, items } = setup();

      toggleAll();

      expect(result.current.selected).toEqual(new Set(items));
    });

    it('when some thing selected, selects everything', async () => {
      const { result, toggleAll, toggleSingle, items } = setup();

      toggleSingle(items[0]);
      toggleAll();

      expect(result.current.selected).toEqual(new Set(items));
    });

    it('when everything selected, selects everything', async () => {
      const { result, toggleAll } = setup();

      toggleAll();
      toggleAll();

      expect(result.current.selected).toEqual(new Set([]));
    });
  });

  describe('clearAll', () => {
    it('drops the selection', async () => {
      const { result, toggleSingle, clearAll, items } = setup();

      toggleSingle(items[0]);
      clearAll();

      expect(result.current.selected).toEqual(new Set());
    });
  });

  describe('allSelected', () => {
    it('for no selection - allSelected = false, indeterminate = false', async () => {
      const { result } = setup();

      expect(result.current.allSelected).toEqual(false);
      expect(result.current.allIndeterminate).toEqual(false);
    });

    it('for one selected - allSelected = false, indeterminate = true', async () => {
      const { result, toggleSingle, items } = setup();

      toggleSingle(items[0]);
      expect(result.current.allSelected).toEqual(false);
      expect(result.current.allIndeterminate).toEqual(true);
    });

    it('for all selected - allSelected = true, indeterminate = false', async () => {
      const { result, toggleAll } = setup();

      toggleAll();
      expect(result.current.allSelected).toEqual(true);
      expect(result.current.allIndeterminate).toEqual(false);
    });
  });
});