UNPKG

@shopify/cli-kit

Version:

A set of utilities, interfaces, and models that are common across all the platform features

575 lines (549 loc) 21.7 kB
import { SelectInput } from './SelectInput.js'; import { sendInputAndWait, sendInputAndWaitForChange, waitForInputsToBeReady, render, getLastFrameAfterUnmount, } from '../../testing/ui.js'; import { platformAndArch } from '../../../../public/node/os.js'; import { describe, expect, test, vi } from 'vitest'; import React from 'react'; const ARROW_UP = '\u001B[A'; const ARROW_DOWN = '\u001B[B'; const ENTER = '\r'; describe('SelectInput', async () => { test('move up with up arrow key', async () => { const onChange = vi.fn(); const items = [ { label: 'First', value: 'first', }, { label: 'Second', value: 'second', }, { label: 'Third', value: 'third', }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: onChange })); await waitForInputsToBeReady(); await sendInputAndWaitForChange(renderInstance, ARROW_DOWN); await sendInputAndWaitForChange(renderInstance, ARROW_DOWN); await sendInputAndWaitForChange(renderInstance, ARROW_UP); expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` " First > Second Third Press ↑↓ arrows to select, enter to confirm." `); expect(onChange).toHaveBeenLastCalledWith(items[1]); }); test('move down with down arrow key', async () => { const onChange = vi.fn(); const items = [ { label: 'First', value: 'first', }, { label: 'Second', value: 'second', }, { label: 'Third', value: 'third', }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: onChange })); await waitForInputsToBeReady(); await sendInputAndWaitForChange(renderInstance, ARROW_DOWN); expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` " First > Second Third Press ↑↓ arrows to select, enter to confirm." `); expect(onChange).toHaveBeenCalledWith(items[1]); }); test('throws an error if a key has more than 1 character', async () => { const onChange = vi.fn(); const items = [ { label: 'First', value: 'first', key: 'a', }, { label: 'Second', value: 'second', key: 'b', }, { label: 'Third', value: 'third', key: 'ab', }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: onChange })); expect(getLastFrameAfterUnmount(renderInstance)).toMatch('SelectInput: Keys must be a single character'); }); test("throws an error if an item has key but others don't", async () => { const onChange = vi.fn(); const items = [ { label: 'First', value: 'first', }, { label: 'Second', value: 'second', }, { label: 'Third', value: 'third', key: 'a', }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: onChange })); expect(getLastFrameAfterUnmount(renderInstance)).toMatch('SelectInput: All items must have keys if one does'); }); test('handles pressing non existing keys', async () => { const onChange = vi.fn(); const items = [ { label: 'First', value: 'first', }, { label: 'Second', value: 'second', }, { label: 'Tenth', value: 'tenth', }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: onChange })); await waitForInputsToBeReady(); // nothing changes when pressing a key that doesn't exist await sendInputAndWait(renderInstance, 100, '4'); expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` "> First Second Tenth Press ↑↓ arrows to select, enter to confirm." `); expect(onChange).not.toHaveBeenCalled(); }); const runningOnWindows = platformAndArch().platform === 'windows'; test.skipIf(runningOnWindows)('support groups', async () => { const onChange = vi.fn(); const items = [ { label: 'first', value: 'first', group: 'Automations' }, { label: 'second', value: 'second', group: 'Automations' }, { label: 'third', value: 'third', group: 'Merchant Admin' }, { label: 'fourth', value: 'fourth', group: 'Merchant Admin' }, { label: 'fifth', value: 'fifth' }, { label: 'sixth', value: 'sixth' }, { label: 'seventh', value: 'seventh' }, { label: 'eighth', value: 'eighth' }, { label: 'ninth', value: 'ninth' }, { label: 'tenth', value: 'tenth' }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: onChange })); expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` " Automations > first second Merchant Admin third fourth Other fifth sixth seventh eighth ninth tenth Press ↑↓ arrows to select, enter to confirm." `); await waitForInputsToBeReady(); await sendInputAndWaitForChange(renderInstance, ARROW_DOWN); await sendInputAndWaitForChange(renderInstance, ARROW_DOWN); expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` " Automations first second Merchant Admin > third fourth Other fifth sixth seventh eighth ninth tenth Press ↑↓ arrows to select, enter to confirm." `); expect(onChange).toHaveBeenLastCalledWith(items[2]); }); test.skipIf(runningOnWindows)('respects groupOrder for custom group ordering', async () => { const onChange = vi.fn(); const items = [ { label: 'first', value: 'first', group: 'GroupA' }, { label: 'second', value: 'second', group: 'GroupA' }, { label: 'third', value: 'third', group: 'GroupB' }, { label: 'fourth', value: 'fourth', group: 'GroupB' }, { label: 'fifth', value: 'fifth', group: 'GroupC' }, { label: 'sixth', value: 'sixth', group: 'GroupC' }, ]; // Custom order: GroupC first, then GroupB, then GroupA const groupOrder = ['GroupC', 'GroupB', 'GroupA']; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: onChange, groupOrder: groupOrder })); expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` " GroupC > fifth sixth GroupB third fourth GroupA first second Press ↑↓ arrows to select, enter to confirm." `); expect(onChange).not.toHaveBeenCalled(); }); test.skipIf(runningOnWindows)('ensures "Other" group always appears last with groupOrder', async () => { const onChange = vi.fn(); const items = [ { label: 'item1', value: '1', group: 'GroupA' }, { label: 'item2', value: '2', group: 'GroupC' }, { label: 'item3', value: '3' }, { label: 'item4', value: '4', group: 'GroupX' }, { label: 'item5', value: '5' }, ]; // GroupOrder specifies: GroupC first, then GroupA // GroupX is not specified, so should come before "Other" but after specified groups const groupOrder = ['GroupC', 'GroupA']; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: onChange, groupOrder: groupOrder })); expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` " GroupC > item2 GroupA item1 GroupX item4 Other item3 item5 Press ↑↓ arrows to select, enter to confirm." `); expect(onChange).not.toHaveBeenCalled(); }); test('allows disabling shortcuts', async () => { const onChange = vi.fn(); const items = [ { label: 'First', value: 'first', }, { label: 'Second', value: 'second', }, { label: 'Third', value: 'third', }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: onChange, enableShortcuts: false })); await waitForInputsToBeReady(); // input doesn't change on shortcut pressed await sendInputAndWait(renderInstance, 100, '2'); expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` "> First Second Third Press ↑↓ arrows to select, enter to confirm." `); expect(onChange).not.toHaveBeenCalled(); }); test('accepts a default value', async () => { const items = [ { label: 'First', value: 'first', }, { label: 'Second', value: 'second', }, { label: 'Third', value: 'third', }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: () => { }, defaultValue: "second" })); await waitForInputsToBeReady(); expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` " First > Second Third Press ↑↓ arrows to select, enter to confirm." `); }); test('shows if there are more pages', async () => { const items = [ { label: 'First', value: 'first', }, { label: 'Second', value: 'second', }, { label: 'Third', value: 'third', }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: () => { }, morePagesMessage: "Keep scrolling to see more items", hasMorePages: true })); await waitForInputsToBeReady(); expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` "> First Second Third Press ↑↓ arrows to select, enter to confirm. 1-3 of many Keep scrolling to see more items" `); }); test('supports a limit of items to show', async () => { const items = [ { label: 'first', value: 'first' }, { label: 'second', value: 'second' }, { label: 'third', value: 'third' }, { label: 'fourth', value: 'fourth' }, { label: 'fifth', value: 'fifth', group: 'Automations' }, { label: 'sixth', value: 'sixth', group: 'Automations' }, { label: 'seventh', value: 'seventh' }, { label: 'eighth', value: 'eighth', group: 'Merchant Admin' }, { label: 'ninth', value: 'ninth', group: 'Merchant Admin' }, { label: 'tenth', value: 'tenth' }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: () => { }, availableLines: 10 })); expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` " Automations   > fifth   sixth     Merchant Admin   eighth   ninth     Other   first   Press ↑↓ arrows to select, enter to confirm." `); await waitForInputsToBeReady(); await sendInputAndWaitForChange(renderInstance, ARROW_DOWN); await sendInputAndWaitForChange(renderInstance, ARROW_DOWN); await sendInputAndWaitForChange(renderInstance, ARROW_DOWN); await sendInputAndWaitForChange(renderInstance, ARROW_DOWN); await sendInputAndWaitForChange(renderInstance, ARROW_DOWN); expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` " Automations   sixth     Merchant Admin   eighth   ninth     Other   first   > second   Press ↑↓ arrows to select, enter to confirm." `); }); test('pressing enter calls onSubmit on the default option', async () => { const onSubmit = vi.fn(); const items = [ { label: 'First', value: 'first', }, { label: 'Second', value: 'second', }, { label: 'Third', value: 'third', }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: () => { }, onSubmit: onSubmit })); await waitForInputsToBeReady(); await sendInputAndWait(renderInstance, 100, ENTER); expect(onSubmit).toHaveBeenCalledWith(items[0]); }); test('pressing enter calls onSubmit on the selected option', async () => { const onSubmit = vi.fn(); const items = [ { label: 'First', value: 'first', }, { label: 'Second', value: 'second', }, { label: 'Third', value: 'third', }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: () => { }, onSubmit: onSubmit })); await waitForInputsToBeReady(); await sendInputAndWait(renderInstance, 100, ARROW_DOWN); await sendInputAndWait(renderInstance, 100, ENTER); expect(onSubmit).toHaveBeenCalledWith(items[1]); }); test('using a shortcut calls onSubmit', async () => { const onSubmit = vi.fn(); const items = [ { label: 'First', value: 'first', key: 'f', }, { label: 'Second', value: 'second', key: 's', }, { label: 'Third', value: 'third', key: 't', }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: () => { }, onSubmit: onSubmit })); await waitForInputsToBeReady(); await sendInputAndWait(renderInstance, 100, 's'); expect(onSubmit).toHaveBeenCalledWith(items[1]); }); test('supports disabled options', async () => { const onSubmit = vi.fn(); const items = [ { label: 'First', value: 'first', }, { label: 'Second', value: 'second', disabled: true, }, { label: 'Third', value: 'third', }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: () => { }, onSubmit: onSubmit })); await waitForInputsToBeReady(); await sendInputAndWaitForChange(renderInstance, ARROW_DOWN); expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` " First Second > Third Press ↑↓ arrows to select, enter to confirm." `); await sendInputAndWait(renderInstance, 100, ENTER); expect(onSubmit).toHaveBeenCalledWith(items[2]); }); test('default value will be skipped if the option is disabled', async () => { const onSubmit = vi.fn(); const items = [ { label: 'First', value: 'first', }, { label: 'Second', value: 'second', disabled: true, }, { label: 'Third', value: 'third', }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: () => { }, onSubmit: onSubmit, defaultValue: "second" })); expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` "> First Second Third Press ↑↓ arrows to select, enter to confirm." `); await waitForInputsToBeReady(); await sendInputAndWait(renderInstance, 100, ENTER); expect(onSubmit).toHaveBeenCalledWith(items[0]); }); test('selects the next non-disabled option if the first option is disabled', async () => { const onSubmit = vi.fn(); const items = [ { label: 'First', value: 'first', key: 'f', disabled: true, }, { label: 'Second', value: 'second', key: 's', disabled: true, }, { label: 'Third', value: 'third', key: 't', }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: () => { }, onSubmit: onSubmit })); await waitForInputsToBeReady(); expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` " (f) First (s) Second > (t) Third Press ↑↓ arrows to select, enter or a shortcut to confirm." `); await sendInputAndWait(renderInstance, 100, ENTER); expect(onSubmit).toHaveBeenCalledWith(items[2]); }); test("doesn't allow submitting disabled options with shortcuts", async () => { const onSubmit = vi.fn(); const items = [ { label: 'First', value: 'first', key: 'f', }, { label: 'Second', value: 'second', key: 's', disabled: true, }, { label: 'Third', value: 'third', key: 't', }, ]; const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: () => { }, onSubmit: onSubmit })); await waitForInputsToBeReady(); await sendInputAndWait(renderInstance, 100, 's'); expect(onSubmit).not.toHaveBeenCalled(); }); }); //# sourceMappingURL=SelectInput.test.js.map