diff --git a/pkg/lib/cockpit-components-file-autocomplete.jsx b/pkg/lib/cockpit-components-file-autocomplete.jsx index f09002ba0539..7891df27dce3 100644 --- a/pkg/lib/cockpit-components-file-autocomplete.jsx +++ b/pkg/lib/cockpit-components-file-autocomplete.jsx @@ -19,9 +19,9 @@ import cockpit from "cockpit"; import React from "react"; -import { Select, SelectOption } from "@patternfly/react-core/dist/esm/deprecated/components/Select/index.js"; import PropTypes from "prop-types"; import { debounce } from 'throttle-debounce'; +import { TypeaheadSelect } from "cockpit-components-typeahead-select"; const _ = cockpit.gettext; @@ -31,17 +31,11 @@ export class FileAutoComplete extends React.Component { this.state = { directory: '', // The current directory we list files/dirs from displayFiles: [], - isOpen: false, value: this.props.value || null, }; - this.typeaheadInputValue = ""; this.allowFilesUpdate = true; - this.updateFiles = this.updateFiles.bind(this); - this.finishUpdate = this.finishUpdate.bind(this); - this.onToggle = this.onToggle.bind(this); this.clearSelection = this.clearSelection.bind(this); - this.onCreateOption = this.onCreateOption.bind(this); this.onPathChange = (value) => { if (!value) { @@ -49,8 +43,6 @@ export class FileAutoComplete extends React.Component { return; } - this.typeaheadInputValue = value; - const cb = (dirPath) => this.updateFiles(dirPath == '' ? '/' : dirPath); let path = value; @@ -89,12 +81,6 @@ export class FileAutoComplete extends React.Component { this.allowFilesUpdate = false; } - onCreateOption(newValue) { - this.setState(prevState => ({ - displayFiles: [...prevState.displayFiles, { type: "file", path: newValue }] - })); - } - updateFiles(path) { if (this.state.directory == path) return; @@ -152,17 +138,9 @@ export class FileAutoComplete extends React.Component { }); } - onToggle(_, isOpen) { - this.setState({ isOpen }); - } - clearSelection() { - this.typeaheadInputValue = ""; this.updateFiles("/"); - this.setState({ - value: null, - isOpen: false - }); + this.setState({ value: null }); this.props.onChange('', null); } @@ -170,32 +148,32 @@ export class FileAutoComplete extends React.Component { const placeholder = this.props.placeholder || _("Path to file"); const selectOptions = this.state.displayFiles - .map(option => ); + .map(option => ({ value: option.path, content: option.path, className: option.type })); + return ( - + { + // Try to list again when + // opening. Calling onPathChange here + // usually does nothing, except when + // there was an error earlier. + if (isOpen) + this.onPathChange(this.state.value); + }} + selected={this.state.value} + onSelect={(_, value) => { + this.setState({ value }); + this.onPathChange(value); + this.props.onChange(value || '', null); + }} + onClearSelection={this.clearSelection} + isCreatable={this.props.isOptionCreatable} + createOptionMessage={val => cockpit.format(_("Create $0"), val)} + selectOptions={selectOptions} /> ); } } diff --git a/test/common/testlib.py b/test/common/testlib.py index f0d1161370ad..bd6a4edf6c6f 100644 --- a/test/common/testlib.py +++ b/test/common/testlib.py @@ -736,10 +736,12 @@ def set_input_text( self.wait_val(selector, val) def set_file_autocomplete_val(self, group_identifier: str, location: str) -> None: - self.set_input_text(f"{group_identifier} .pf-v5-c-select__toggle-typeahead input", location) - # click away the selection list, to force a state update - self.click(f"{group_identifier} .pf-v5-c-select__toggle-typeahead") - self.wait_not_present(f"{group_identifier} .pf-v5-c-select__menu") + self.set_input_text(f"{group_identifier} .pf-v5-c-menu-toggle input", location) + # select the file + self.wait_text(f"{group_identifier} ul li:nth-child(1) button", location) + self.click(f"{group_identifier} ul li:nth-child(1) button") + self.wait_not_present(f"{group_identifier} .pf-v5-c-menu") + self.wait_val(f"{group_identifier} .pf-v5-c-menu-toggle input", location) @contextlib.contextmanager def wait_timeout(self, timeout: int) -> Iterator[None]: diff --git a/test/verify/check-pages b/test/verify/check-pages index 25502cde2de5..ea321e3b3c8b 100755 --- a/test/verify/check-pages +++ b/test/verify/check-pages @@ -604,29 +604,28 @@ OnCalendar=daily b.focus("#demo-file-ac input[type=text]") b.input_text(stuff + "/") # need to wait for the widget's "fast typing" inhibition delay to trigger the completion popup - b.wait_in_text("#file-autocomplete-widget li:nth-of-type(1) button", stuff + "/") - b.wait_in_text("#file-autocomplete-widget li:nth-of-type(2) button", "dir/") - b.wait_in_text("#file-autocomplete-widget li:nth-of-type(3) button", "dir1/") - b.wait_in_text("#file-autocomplete-widget li:nth-of-type(4) button", "file1.txt") - b.click("#file-autocomplete-widget li:nth-of-type(2) button") + b.wait_in_text("#demo-file-ac li:nth-of-type(1) button", stuff + "/") + b.wait_in_text("#demo-file-ac li:nth-of-type(2) button", "dir/") + b.wait_in_text("#demo-file-ac li:nth-of-type(3) button", "dir1/") + b.wait_in_text("#demo-file-ac li:nth-of-type(4) button", "file1.txt") + b.click("#demo-file-ac li:nth-of-type(2) button") # clear the file completion widget b.click("#demo-file-ac div:first-of-type div:first-of-type button:nth-of-type(1)") # test if input matches one entry, but is the prefix of other entry, widget should not descend into directory b.focus("#demo-file-ac input[type=text]") b.input_text(stuff + "/dir") - b.wait_in_text("#file-autocomplete-widget li:nth-of-type(1) button", stuff + "/dir") - b.wait_in_text("#file-autocomplete-widget li:nth-of-type(2) button", stuff + "/dir1") + b.wait_in_text("#demo-file-ac li:nth-of-type(1) button", stuff + "/dir") + b.wait_in_text("#demo-file-ac li:nth-of-type(2) button", stuff + "/dir1") # clear the file completion widget b.click("#demo-file-ac div:first-of-type div:first-of-type button:nth-of-type(1)") - b.wait_not_present("#file-autocomplete-widget li") b.focus("#demo-file-ac input[type=text]") b.input_text(stuff + "/") - b.wait_in_text("#file-autocomplete-widget li:nth-of-type(1) button", stuff + "/") - b.wait_in_text("#file-autocomplete-widget li:nth-of-type(4) button", "file1.txt") - b.click("#file-autocomplete-widget li:nth-of-type(4) button") - b.wait_not_present("#file-autocomplete-widget li") + b.wait_in_text("#demo-file-ac li:nth-of-type(1) button", stuff + "/") + b.wait_in_text("#demo-file-ac li:nth-of-type(4) button", "file1.txt") + b.click("#demo-file-ac li:nth-of-type(4) button") + b.wait_not_present("#demo-file-ac li") # now update file1, check robustness with dynamic events m.execute(f"touch {stuff}/file1.txt") @@ -634,10 +633,10 @@ OnCalendar=daily time.sleep(1) b.key("Backspace", 5) # input is now $stuff/file - b.wait_in_text("#file-autocomplete-widget li:nth-of-type(1) button", "file1.txt") + b.wait_in_text("#demo-file-ac li:nth-of-type(1) button", "file1.txt") b.key("Backspace", 4) # input is now $stuff/, so all listings should be available - b.wait_in_text("#file-autocomplete-widget li:nth-of-type(4) button", "file1.txt") + b.wait_in_text("#demo-file-ac li:nth-of-type(4) button", "file1.txt") # add new file m.execute(f"touch {stuff}/other") @@ -649,10 +648,10 @@ OnCalendar=daily b.key("Backspace", 6) time.sleep(1) b.input_text("stuff/") - b.wait_in_text("#file-autocomplete-widget li:nth-of-type(5) button", "other") + b.wait_in_text("#demo-file-ac li:nth-of-type(5) button", "other") # close the selector b.click("#demo-file-ac input") - b.wait_not_present("#file-autocomplete-widget") + b.wait_not_present("#demo-file-ac li") # Create test folder with known files m.execute("mkdir /home/admin/newdir") @@ -664,18 +663,15 @@ OnCalendar=daily b.wait_val("#demo-file-ac-preselected input", "/home/admin/newdir/file1") # open the selector b.click("#demo-file-ac-preselected input") - b.wait_visible("#file-autocomplete-widget-preselected") + b.wait_visible("#demo-file-ac-preselected .pf-v5-c-menu") # close and open again to reload the dir (which just got created) - b.click("#demo-file-ac-preselected input") - b.wait_not_present("#file-autocomplete-widget-preselected") + b.click("#demo-file-ac-preselected .pf-v5-c-menu-toggle__button") + b.wait_not_present("#demo-file-ac-preselected .pf-v5-c-menu") b.click("#demo-file-ac-preselected input") # selection has all the files in the directory paths = ["/home/admin/newdir", "/home/admin/newdir/dir1", "/home/admin/newdir/dir2", "/home/admin/newdir/file1", "/home/admin/newdir/file2"] for i in range(5): - b.wait_in_text(f"#file-autocomplete-widget-preselected li:nth-of-type({i + 1}) button", paths[i]) - # clicking on input again hides selector dropdown again - b.click("#demo-file-ac-preselected input") - b.wait_not_present("#file-autocomplete-widget-preselected") + b.wait_in_text(f"#demo-file-ac-preselected li:nth-of-type({i + 1}) button", paths[i]) @testlib.skipOstree("No PCP available") @testlib.skipImage("pcp not currently in testing", "debian-testing") @@ -956,7 +952,7 @@ OnCalendar=daily # Upload permission error b.drop_superuser() - b.set_file_autocomplete_val("#demo-upload", "/root/") + b.set_file_autocomplete_val("#demo-upload", "/var/") b.upload_files("#demo-upload input[type='file']", [test_upload_file]) b.wait_in_text(".pf-v5-c-alert", "Not permitted to perform this action") diff --git a/test/verify/check-shell-keys b/test/verify/check-shell-keys index 17c2030bef66..474f57ede02b 100755 --- a/test/verify/check-shell-keys +++ b/test/verify/check-shell-keys @@ -304,18 +304,17 @@ session optional pam_ssh_add.so b.wait_visible("#credential-keys") b.wait_not_present("#ssh-file-add") b.click("#ssh-file-add-custom") - b.set_file_autocomplete_val("#ssh-file-add-key", "/bad") + b.set_file_autocomplete_val("#ssh-file-add-key", "/etc/") b.click("#ssh-file-add") b.wait_text("#credentials-modal .pf-m-error > .pf-v5-c-helper-text__item-text", "Not a valid private key") - b.click("#credentials-modal .pf-v5-c-select__toggle-typeahead") - b.set_input_text("#credentials-modal .pf-v5-c-select__toggle-typeahead input", "/var/test/") - b.wait_in_text("#credentials-modal .pf-v5-c-select__menu-item.pf-m-disabled", "No such file or directory") - b.focus("#credentials-modal .pf-v5-c-select__toggle-typeahead input") + b.set_input_text("#credentials-modal .pf-v5-c-menu-toggle input", "/var/test/") + b.wait_in_text("#credentials-modal .pf-v5-c-menu__list-item.pf-m-aria-disabled", "No such file or directory") + b.focus("#credentials-modal .pf-v5-c-menu-toggle input") b.key("Backspace", 5) - b.wait_visible("#credentials-modal .pf-v5-c-select__menu-item.directory:contains('/var/lib/')") - b.click("#credentials-modal .pf-v5-c-select__toggle-clear") - b.wait_val("#credentials-modal .pf-v5-c-select__toggle-typeahead input", "") + b.wait_visible("#credentials-modal .pf-v5-c-menu__list-item.directory:contains('/var/lib/')") + b.click("#credentials-modal .pf-v5-c-menu-toggle button[aria-label='Clear input value']") + b.wait_val("#credentials-modal .pf-v5-c-menu-toggle input", "") b.set_file_autocomplete_val("#ssh-file-add-key", "/tmp/new.rsa") b.click("#ssh-file-add")