Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/static_src/react/ReactAutocomplete.test.tsx b/static_src/react/ReactAutocomplete.test.tsx
new file mode 100644
index 0000000..a1e7c62
--- /dev/null
+++ b/static_src/react/ReactAutocomplete.test.tsx
@@ -0,0 +1,311 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+import {assert} from 'chai';
+import React from 'react';
+import sinon from 'sinon';
+import {fireEvent, render} from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import {ReactAutocomplete, MAX_AUTOCOMPLETE_OPTIONS}
+  from './ReactAutocomplete.tsx';
+
+/**
+ * Cleans autocomplete dropdown from the DOM for the next test.
+ * @param input The autocomplete element to remove the dropdown for.
+ */
+ const cleanAutocomplete = (input: ReactAutocomplete) => {
+  fireEvent.change(input, {target: {value: ''}});
+  fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
+};
+
+xdescribe('ReactAutocomplete', () => {
+  it('renders', async () => {
+    const {container} = render(<ReactAutocomplete label="cool" options={[]} />);
+
+    assert.isNotNull(container.querySelector('input'));
+  });
+
+  it('placeholder renders', async () => {
+    const {container} = render(<ReactAutocomplete
+      placeholder="penguins"
+      options={['']}
+    />);
+
+    const input = container.querySelector('input');
+    assert.isNotNull(input);
+    if (!input) return;
+
+    assert.strictEqual(input?.placeholder, 'penguins');
+  });
+
+  it('filterOptions empty input value', async () => {
+    const {container} = render(<ReactAutocomplete
+      label="cool"
+      options={['option 1 label']}
+    />);
+
+    const input = container.querySelector('input');
+    assert.isNotNull(input);
+    if (!input) return;
+
+    assert.strictEqual(input?.value, '');
+
+    fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
+    assert.strictEqual(input?.value, '');
+  });
+
+  it('filterOptions truncates values', async () => {
+    const options = [];
+
+    // a0@test.com, a1@test.com, a2@test.com, ...
+    for (let i = 0; i <= MAX_AUTOCOMPLETE_OPTIONS; i++) {
+      options.push(`a${i}@test.com`);
+    }
+
+    const {container} = render(<ReactAutocomplete
+      label="cool"
+      options={options}
+    />);
+
+    const input = container.querySelector('input');
+    assert.isNotNull(input);
+    if (!input) return;
+
+    userEvent.type(input, 'a');
+
+    const results = document.querySelectorAll('.autocomplete-option');
+
+    assert.equal(results.length, MAX_AUTOCOMPLETE_OPTIONS);
+
+    // Clean up autocomplete dropdown from the DOM for the next test.
+    cleanAutocomplete(input);
+  });
+
+  it('filterOptions label matching', async () => {
+    const {container} = render(<ReactAutocomplete
+      label="cool"
+      options={['option 1 label']}
+    />);
+
+    const input = container.querySelector('input');
+    assert.isNotNull(input);
+    if (!input) return;
+
+    assert.strictEqual(input?.value, '');
+
+    userEvent.type(input, 'lab');
+    assert.strictEqual(input?.value, 'lab');
+
+    fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
+
+    assert.strictEqual(input?.value, 'option 1 label');
+  });
+
+  it('filterOptions description matching', async () => {
+    const {container} = render(<ReactAutocomplete
+      label="cool"
+      getOptionDescription={() => 'penguin apples'}
+      options={['lol']}
+    />);
+
+    const input = container.querySelector('input');
+    assert.isNotNull(input);
+    if (!input) return;
+
+    assert.strictEqual(input?.value, '');
+
+    userEvent.type(input, 'app');
+    assert.strictEqual(input?.value, 'app');
+
+    fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
+    assert.strictEqual(input?.value, 'lol');
+  });
+
+  it('filterOptions no match', async () => {
+    const {container} = render(<ReactAutocomplete
+      label="cool"
+      options={[]}
+    />);
+
+    const input = container.querySelector('input');
+    assert.isNotNull(input);
+    if (!input) return;
+
+    assert.strictEqual(input?.value, '');
+
+    userEvent.type(input, 'foobar');
+    assert.strictEqual(input?.value, 'foobar');
+
+    fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
+    assert.strictEqual(input?.value, 'foobar');
+  });
+
+  it('onChange callback is called', async () => {
+    const onChangeStub = sinon.stub();
+
+    const {container} = render(<ReactAutocomplete
+      label="cool"
+      options={[]}
+      onChange={onChangeStub}
+    />);
+
+    const input = container.querySelector('input');
+    assert.isNotNull(input);
+    if (!input) return;
+
+    sinon.assert.notCalled(onChangeStub);
+
+    userEvent.type(input, 'foobar');
+    sinon.assert.notCalled(onChangeStub);
+
+    fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
+    sinon.assert.calledOnce(onChangeStub);
+
+    assert.equal(onChangeStub.getCall(0).args[1], 'foobar');
+  });
+
+  it('onChange excludes fixed values', async () => {
+    const onChangeStub = sinon.stub();
+
+    const {container} = render(<ReactAutocomplete
+      label="cool"
+      options={['cute owl']}
+      multiple={true}
+      fixedValues={['immortal penguin']}
+      onChange={onChangeStub}
+    />);
+
+    const input = container.querySelector('input');
+    assert.isNotNull(input);
+    if (!input) return;
+
+    fireEvent.keyDown(input, {key: 'Backspace', code: 'Backspace'});
+    fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
+
+    sinon.assert.calledWith(onChangeStub, sinon.match.any, []);
+  });
+
+  it('pressing space creates new chips', async () => {
+    const onChangeStub = sinon.stub();
+
+    const {container} = render(<ReactAutocomplete
+      label="cool"
+      options={['cute owl']}
+      multiple={true}
+      onChange={onChangeStub}
+    />);
+
+    const input = container.querySelector('input');
+    assert.isNotNull(input);
+    if (!input) return;
+
+    sinon.assert.notCalled(onChangeStub);
+
+    userEvent.type(input, 'foobar');
+    sinon.assert.notCalled(onChangeStub);
+
+    fireEvent.keyDown(input, {key: ' ', code: 'Space'});
+    sinon.assert.calledOnce(onChangeStub);
+
+    assert.deepEqual(onChangeStub.getCall(0).args[1], ['foobar']);
+  });
+
+  it('_renderOption shows user input', async () => {
+    const {container} = render(<ReactAutocomplete
+      label="cool"
+      options={['cute@owl.com']}
+    />);
+
+    const input = container.querySelector('input');
+    assert.isNotNull(input);
+    if (!input) return;
+
+    userEvent.type(input, 'ow');
+
+    const options = document.querySelectorAll('.autocomplete-option');
+
+    // Options: cute@owl.com
+    assert.deepEqual(options.length, 1);
+    assert.equal(options[0].textContent, 'cute@owl.com');
+
+    cleanAutocomplete(input);
+  });
+
+  it('_renderOption hides duplicate user input', async () => {
+    const {container} = render(<ReactAutocomplete
+      label="cool"
+      options={['cute@owl.com']}
+    />);
+
+    const input = container.querySelector('input');
+    assert.isNotNull(input);
+    if (!input) return;
+
+    userEvent.type(input, 'cute@owl.com');
+
+    const options = document.querySelectorAll('.autocomplete-option');
+
+    // Options: cute@owl.com
+    assert.equal(options.length, 1);
+
+    assert.equal(options[0].textContent, 'cute@owl.com');
+
+    cleanAutocomplete(input);
+  });
+
+  it('_renderOption highlights matching text', async () => {
+    const {container} = render(<ReactAutocomplete
+      label="cool"
+      options={['cute@owl.com']}
+    />);
+
+    const input = container.querySelector('input');
+    assert.isNotNull(input);
+    if (!input) return;
+
+    userEvent.type(input, 'ow');
+
+    const option = document.querySelector('.autocomplete-option');
+    const match = option?.querySelector('strong');
+
+    assert.isNotNull(match);
+    assert.equal(match?.innerText, 'ow');
+
+    // Description is not rendered.
+    assert.equal(option?.querySelectorAll('span').length, 1);
+    assert.equal(option?.querySelectorAll('strong').length, 1);
+
+    cleanAutocomplete(input);
+  });
+
+  it('_renderOption highlights matching description', async () => {
+    const {container} = render(<ReactAutocomplete
+      label="cool"
+      getOptionDescription={() => 'penguin of-doom'}
+      options={['cute owl']}
+    />);
+
+    const input = container.querySelector('input');
+    assert.isNotNull(input);
+    if (!input) return;
+
+    userEvent.type(input, 'do');
+
+    const option = document.querySelector('.autocomplete-option');
+    const match = option?.querySelector('strong');
+
+    assert.isNotNull(match);
+    assert.equal(match?.innerText, 'do');
+
+    assert.equal(option?.querySelectorAll('span').length, 2);
+    assert.equal(option?.querySelectorAll('strong').length, 1);
+
+    cleanAutocomplete(input);
+  });
+
+  it('_renderTags disables fixedValues', async () => {
+    // TODO(crbug.com/monorail/9393): Add this test once we have a way to stub
+    // out dependent components.
+  });
+});