MOBILE-2652 glossary: Edit attachments
This commit is contained in:
		
							parent
							
								
									9391fe4122
								
							
						
					
					
						commit
						d2d8a814f6
					
				
							
								
								
									
										11
									
								
								gulpfile.js
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								gulpfile.js
									
									
									
									
									
								
							| @ -71,5 +71,14 @@ gulp.task('watch', () => { | ||||
| }); | ||||
| 
 | ||||
| gulp.task('watch-behat', () => { | ||||
|     gulp.watch(['./src/**/*.feature', './src/**/*.png', './local_moodleappbehat'], { interval: 500 }, gulp.parallel('behat')); | ||||
|     gulp.watch( | ||||
|         [ | ||||
|             './src/**/*.feature', | ||||
|             './src/**/tests/behat/fixtures/**', | ||||
|             './src/**/tests/behat/snapshots/**', | ||||
|             './local_moodleappbehat', | ||||
|         ], | ||||
|         { interval: 500 }, | ||||
|         gulp.parallel('behat') | ||||
|     ); | ||||
| }); | ||||
|  | ||||
| @ -44,27 +44,21 @@ class behat_app extends behat_app_helper { | ||||
|         ], | ||||
|     ]; | ||||
| 
 | ||||
|     protected $featurepath = ''; | ||||
|     protected $windowsize = '360x720'; | ||||
| 
 | ||||
|     /** | ||||
|      * @BeforeScenario | ||||
|      */ | ||||
|     public function before_scenario(ScenarioScope $scope) { | ||||
|         if (!$scope->getFeature()->hasTag('app')) { | ||||
|         $feature = $scope->getFeature(); | ||||
| 
 | ||||
|         if (!$feature->hasTag('app')) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         global $CFG; | ||||
| 
 | ||||
|         $performanceLogs = $CFG->behat_profiles['default']['capabilities']['extra_capabilities']['goog:loggingPrefs']['performance'] ?? null; | ||||
| 
 | ||||
|         if ($performanceLogs !== 'ALL') { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Enable DB Logging only for app tests with performance logs activated.
 | ||||
|         $this->getSession()->visit($this->get_app_url() . '/assets/env.json'); | ||||
|         $this->execute_script("document.cookie = 'MoodleAppDBLoggingEnabled=true;path=/';"); | ||||
|         $this->featurepath = dirname($feature->getFile()); | ||||
|         $this->configure_performance_logs(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -89,6 +83,23 @@ class behat_app extends behat_app_helper { | ||||
|         $this->enter_site(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Configure performance logs. | ||||
|      */ | ||||
|     protected function configure_performance_logs() { | ||||
|         global $CFG; | ||||
| 
 | ||||
|         $performanceLogs = $CFG->behat_profiles['default']['capabilities']['extra_capabilities']['goog:loggingPrefs']['performance'] ?? null; | ||||
| 
 | ||||
|         if ($performanceLogs !== 'ALL') { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Enable DB Logging only for app tests with performance logs activated.
 | ||||
|         $this->getSession()->visit($this->get_app_url() . '/assets/env.json'); | ||||
|         $this->execute_script("document.cookie = 'MoodleAppDBLoggingEnabled=true;path=/';"); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check whether the current page is the login form. | ||||
|      */ | ||||
| @ -778,6 +789,35 @@ class behat_app extends behat_app_helper { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Uploads a file to a file input, the file path should be relative to a fixtures folder next to the feature file. | ||||
|      * The ìnput locator can match a container with a file input inside, it doesn't have to be the input itself. | ||||
|      * | ||||
|      * @Given /^I upload "((?:[^"]|\\")+)" to (".+") in the app$/ | ||||
|      * @param string $filename | ||||
|      * @param string $inputlocator | ||||
|      */ | ||||
|     public function i_upload_a_file_in_the_app(string $filename, string $inputlocator) { | ||||
|         $filepath = str_replace('/', DIRECTORY_SEPARATOR, "{$this->featurepath}/fixtures/$filename"); | ||||
|         $inputlocator = $this->parse_element_locator($inputlocator); | ||||
| 
 | ||||
|         $id = $this->spin(function() use ($inputlocator) { | ||||
|             $result = $this->runtime_js("getFileInputId($inputlocator)"); | ||||
| 
 | ||||
|             if (str_starts_with($result, 'ERROR')) { | ||||
|                 throw new DriverException('Error finding input - ' . $result); | ||||
|             } | ||||
| 
 | ||||
|             return $result; | ||||
|         }); | ||||
| 
 | ||||
|         $this->wait_for_pending_js(); | ||||
| 
 | ||||
|         $fileinput = $this ->getSession()->getPage()->findById($id); | ||||
| 
 | ||||
|         $fileinput->attachFile($filepath); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Checks a field matches a certain value in the app. | ||||
|      * | ||||
|  | ||||
| @ -33,7 +33,7 @@ async function main() { | ||||
|         : []; | ||||
| 
 | ||||
|     if (!existsSync(pluginPath)) { | ||||
|         mkdirSync(pluginPath); | ||||
|         mkdirSync(pluginPath, { recursive: true }); | ||||
|     } else { | ||||
|         // Empty directory, except the excluding list.
 | ||||
|         const excludeFromErase = [ | ||||
| @ -76,21 +76,29 @@ async function main() { | ||||
|     }; | ||||
|     writeFileSync(pluginFilePath, replaceArguments(fileContents, replacements)); | ||||
| 
 | ||||
|     // Copy feature and snapshot files.
 | ||||
|     // Copy features, snapshots, and fixtures.
 | ||||
|     if (!excludeFeatures) { | ||||
|         const behatTempFeaturesPath = `${pluginPath}/behat-tmp`; | ||||
|         copySync(projectPath('src'), behatTempFeaturesPath, { filter: shouldCopyFileOrDirectory }); | ||||
| 
 | ||||
|         const behatFeaturesPath = `${pluginPath}/tests/behat`; | ||||
|         if (!existsSync(behatFeaturesPath)) { | ||||
|             mkdirSync(behatFeaturesPath, {recursive: true}); | ||||
|             mkdirSync(behatFeaturesPath, { recursive: true }); | ||||
|         } | ||||
| 
 | ||||
|         for await (const file of getDirectoryFiles(behatTempFeaturesPath)) { | ||||
|             const filePath = dirname(file); | ||||
|             const snapshotsIndex = file.indexOf('/tests/behat/snapshots/'); | ||||
|             const fixturesIndex = file.indexOf('/tests/behat/fixtures/'); | ||||
| 
 | ||||
|             if (filePath.endsWith('/tests/behat/snapshots')) { | ||||
|                 renameSync(file, behatFeaturesPath + '/snapshots/' + basename(file)); | ||||
|             if (snapshotsIndex !== -1) { | ||||
|                 moveFile(file, behatFeaturesPath + '/snapshots/' + file.slice(snapshotsIndex + 23)); | ||||
| 
 | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (fixturesIndex !== -1) { | ||||
|                 moveFile(file, behatFeaturesPath + '/fixtures/' + file.slice(fixturesIndex + 22)); | ||||
| 
 | ||||
|                 continue; | ||||
|             } | ||||
| @ -103,7 +111,7 @@ async function main() { | ||||
|             const searchRegExp = /\//g; | ||||
|             const prefix = relative(behatTempFeaturesPath, newPath).replace(searchRegExp,'-') || 'core'; | ||||
|             const featureFilename = prefix + '-' + basename(file); | ||||
|             renameSync(file, behatFeaturesPath + '/' + featureFilename); | ||||
|             moveFile(file, behatFeaturesPath + '/' + featureFilename); | ||||
|         } | ||||
| 
 | ||||
|         rmSync(behatTempFeaturesPath, {recursive: true}); | ||||
| @ -115,7 +123,8 @@ function shouldCopyFileOrDirectory(path) { | ||||
| 
 | ||||
|     return stats.isDirectory() | ||||
|         || extname(path) === '.feature' | ||||
|         || extname(path) === '.png'; | ||||
|         || path.includes('/tests/behat/snapshots') | ||||
|         || path.includes('/tests/behat/fixtures'); | ||||
| } | ||||
| 
 | ||||
| function isExcluded(file, exclusions) { | ||||
| @ -127,6 +136,16 @@ function fail(message) { | ||||
|     process.exit(1); | ||||
| } | ||||
| 
 | ||||
| function moveFile(from, to) { | ||||
|     const targetDir = dirname(to); | ||||
| 
 | ||||
|     if (!existsSync(targetDir)) { | ||||
|         mkdirSync(targetDir, { recursive: true }); | ||||
|     } | ||||
| 
 | ||||
|     renameSync(from, to); | ||||
| } | ||||
| 
 | ||||
| function guessPluginPath() { | ||||
|     if (process.env.MOODLE_APP_BEHAT_PLUGIN_PATH) { | ||||
|         return process.env.MOODLE_APP_BEHAT_PLUGIN_PATH; | ||||
|  | ||||
| @ -419,6 +419,7 @@ class AddonModGlossaryOfflineFormHandler extends AddonModGlossaryFormHandler { | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async save(glossary: AddonModGlossaryGlossary): Promise<boolean> { | ||||
|         const originalData = this.page.data; | ||||
|         const data = this.page.data; | ||||
| 
 | ||||
|         // Upload attachments first if any.
 | ||||
| @ -428,6 +429,10 @@ class AddonModGlossaryOfflineFormHandler extends AddonModGlossaryFormHandler { | ||||
|             offlineAttachments = await this.storeAttachments(glossary, data.timecreated); | ||||
|         } | ||||
| 
 | ||||
|         if (originalData.concept !== data.concept) { | ||||
|             await AddonModGlossaryHelper.deleteStoredFiles(glossary.id, originalData.concept, data.timecreated); | ||||
|         } | ||||
| 
 | ||||
|         // Save entry data.
 | ||||
|         await this.updateOfflineEntry(glossary, offlineAttachments); | ||||
| 
 | ||||
| @ -653,8 +658,18 @@ class AddonModGlossaryOnlineFormHandler extends AddonModGlossaryFormHandler { | ||||
|         const options = this.getSaveOptions(glossary); | ||||
|         const definition = CoreTextUtils.formatHtmlLines(data.definition); | ||||
| 
 | ||||
|         // Upload attachments, if any.
 | ||||
|         let attachmentsId: number | undefined = undefined; | ||||
| 
 | ||||
|         if (data.attachments.length) { | ||||
|             attachmentsId = await this.uploadAttachments(glossary); | ||||
|         } | ||||
| 
 | ||||
|         // Save entry data.
 | ||||
|         await AddonModGlossary.updateEntry(glossary.id, this.entry.id, data.concept, definition, options); | ||||
|         await AddonModGlossary.updateEntry(glossary.id, this.entry.id, data.concept, definition, options, attachmentsId); | ||||
| 
 | ||||
|         // Delete the local files from the tmp folder.
 | ||||
|         CoreFileUploader.clearTmpFiles(data.attachments); | ||||
| 
 | ||||
|         CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'glossary' }); | ||||
| 
 | ||||
|  | ||||
| @ -73,6 +73,10 @@ | ||||
|                     [componentId]="componentId"> | ||||
|                 </core-file> | ||||
|             </div> | ||||
|             <div *ngIf="offlineEntry && offlineEntryFiles"> | ||||
|                 <core-local-file *ngFor="let file of offlineEntryFiles" [file]="file"> | ||||
|                 </core-local-file> | ||||
|             </div> | ||||
|             <ion-item class="ion-text-wrap" *ngIf="onlineEntry && tagsEnabled && entry && onlineEntry.tags && onlineEntry.tags.length > 0"> | ||||
|                 <ion-label> | ||||
|                     <div slot="start">{{ 'core.tag.tags' | translate }}:</div> | ||||
|  | ||||
| @ -23,6 +23,7 @@ import { CoreCommentsCommentsComponent } from '@features/comments/components/com | ||||
| import { CoreComments } from '@features/comments/services/comments'; | ||||
| import { CoreRatingInfo } from '@features/rating/services/rating'; | ||||
| import { CoreTag } from '@features/tag/services/tag'; | ||||
| import { FileEntry } from '@ionic-native/file/ngx'; | ||||
| import { IonRefresher } from '@ionic/angular'; | ||||
| import { CoreNavigator } from '@services/navigator'; | ||||
| import { CoreNetwork } from '@services/network'; | ||||
| @ -54,6 +55,7 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy { | ||||
|     componentId?: number; | ||||
|     onlineEntry?: AddonModGlossaryEntry; | ||||
|     offlineEntry?: AddonModGlossaryOfflineEntry; | ||||
|     offlineEntryFiles?: FileEntry[]; | ||||
|     entries!: AddonModGlossaryEntryEntriesSwipeManager; | ||||
|     glossary?: AddonModGlossaryGlossary; | ||||
|     entryUpdatedObserver?: CoreEventObserver; | ||||
| @ -263,6 +265,13 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy { | ||||
|             const glossary = await this.loadGlossary(); | ||||
| 
 | ||||
|             this.offlineEntry = await AddonModGlossaryOffline.getOfflineEntry(glossary.id, timecreated); | ||||
|             this.offlineEntryFiles = this.offlineEntry.attachments && this.offlineEntry.attachments.offline > 0 | ||||
|                 ? await AddonModGlossaryHelper.getStoredFiles( | ||||
|                     glossary.id, | ||||
|                     this.offlineEntry.concept, | ||||
|                     timecreated, | ||||
|                 ) | ||||
|                 : undefined; | ||||
|             this.canEdit = true; | ||||
|             this.canDelete = true; | ||||
|         } catch (error) { | ||||
|  | ||||
| @ -930,6 +930,7 @@ export class AddonModGlossaryProvider { | ||||
|      * @param concept Glossary entry concept. | ||||
|      * @param definition Glossary entry concept definition. | ||||
|      * @param options Options for the entry. | ||||
|      * @param attachId Attachments ID (if any attachment). | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      */ | ||||
|     async updateEntry( | ||||
| @ -938,6 +939,7 @@ export class AddonModGlossaryProvider { | ||||
|         concept: string, | ||||
|         definition: string, | ||||
|         options?: Record<string, AddonModGlossaryEntryOption>, | ||||
|         attachId?: number, | ||||
|         siteId?: string, | ||||
|     ): Promise<void> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
| @ -950,6 +952,13 @@ export class AddonModGlossaryProvider { | ||||
|             options: CoreUtils.objectToArrayOfObjects(options || {}, 'name', 'value'), | ||||
|         }; | ||||
| 
 | ||||
|         if (attachId) { | ||||
|             params.options?.push({ | ||||
|                 name: 'attachmentsid', | ||||
|                 value: String(attachId), | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         const response = await site.write<AddonModGlossaryUpdateEntryWSResponse>('mod_glossary_update_entry', params); | ||||
| 
 | ||||
|         if (!response.result) { | ||||
|  | ||||
| @ -154,42 +154,51 @@ Feature: Test basic usage of glossary in app | ||||
|     Then I should find "Garlic" in the app | ||||
|     And I should find "Allium sativum" in the app | ||||
| 
 | ||||
|   Scenario: Edit entries (basic info) | ||||
|   Scenario: Edit entries | ||||
|     Given I entered the glossary activity "Test glossary" on course "Course 1" as "student1" in the app | ||||
| 
 | ||||
|     # Online | ||||
|     When I press "Add a new entry" in the app | ||||
|     And I set the following fields to these values in the app: | ||||
|       | Concept | Cashew | | ||||
|       | Definition | Cashew is a fruit | | ||||
|     And I press "Save" in the app | ||||
|     Then I should find "Cashew" in the app | ||||
| 
 | ||||
|     When I press "Cashew" in the app | ||||
|     When I press "Cucumber" in the app | ||||
|     And I press "Edit entry" in the app | ||||
|     Then the field "Concept" matches value "Cashew" in the app | ||||
|     And the field "Definition" matches value "Cashew is a fruit" in the app | ||||
|     Then the field "Concept" matches value "Cucumber" in the app | ||||
|     And the field "Definition" matches value "Sweet cucumber" in the app | ||||
| 
 | ||||
|     When I set the following fields to these values in the app: | ||||
|       | Concept | Coconut | | ||||
|       | Definition | Coconut is a fruit | | ||||
|     And I press "Add file" in the app | ||||
|     And I upload "stub1.txt" to "File" ".action-sheet-button" in the app | ||||
|     And I press "Add file" in the app | ||||
|     And I upload "stub2.txt" to "File" ".action-sheet-button" in the app | ||||
|     And I press "This entry should be automatically linked" "ion-toggle" in the app | ||||
|     And I press "This entry is case sensitive" "ion-toggle" in the app | ||||
|     And I press "Match whole words only" "ion-toggle" in the app | ||||
|     And I press "Save" in the app | ||||
|     Then I should find "Coconut is a fruit" in the app | ||||
|     But I should not find "Cashew is a fruit" in the app | ||||
|     And I should find "stub1.txt" in the app | ||||
|     And I should find "stub2.txt" in the app | ||||
|     But I should not find "Cucumber is a fruit" in the app | ||||
| 
 | ||||
|     When I press "Edit entry" in the app | ||||
|     Then "This entry should be automatically linked" "ion-toggle" should be selected in the app | ||||
|     Then I should find "stub1.txt" in the app | ||||
|     And I should find "stub2.txt" in the app | ||||
|     And "This entry should be automatically linked" "ion-toggle" should be selected in the app | ||||
|     And "This entry is case sensitive" "ion-toggle" should be selected in the app | ||||
|     And "Match whole words only" "ion-toggle" should be selected in the app | ||||
| 
 | ||||
|     When I press "Save" in the app | ||||
|     And I press the back button in the app | ||||
|     When I press "Delete" within "stub2.txt" "ion-item" in the app | ||||
|     And I press "Delete" near "Are you sure you want to delete this file?" in the app | ||||
|     And I press "Add file" in the app | ||||
|     And I upload "stub3.txt" to "File" ".action-sheet-button" in the app | ||||
|     And I press "Save" in the app | ||||
|     Then I should find "stub1.txt" in the app | ||||
|     And I should find "stub3.txt" in the app | ||||
|     But I should not find "stub2.txt" in the app | ||||
| 
 | ||||
|     When I press the back button in the app | ||||
|     Then I should find "Coconut" in the app | ||||
|     And I should find "Potato" in the app | ||||
|     But I should not find "Cashew" in the app | ||||
|     But I should not find "Cucumber" in the app | ||||
| 
 | ||||
|     # Offline | ||||
|     When I press "Add a new entry" in the app | ||||
| @ -197,6 +206,10 @@ Feature: Test basic usage of glossary in app | ||||
|     And I set the following fields to these values in the app: | ||||
|       | Concept | Broccoli | | ||||
|       | Definition | Brassica oleracea var. italica | | ||||
|     And I press "Add file" in the app | ||||
|     And I upload "stub1.txt" to "File" ".action-sheet-button" in the app | ||||
|     And I press "Add file" in the app | ||||
|     And I upload "stub2.txt" to "File" ".action-sheet-button" in the app | ||||
|     And I press "This entry should be automatically linked" "ion-toggle" in the app | ||||
|     And I press "This entry is case sensitive" "ion-toggle" in the app | ||||
|     And I press "Match whole words only" "ion-toggle" in the app | ||||
| @ -206,10 +219,14 @@ Feature: Test basic usage of glossary in app | ||||
| 
 | ||||
|     When I press "Broccoli" in the app | ||||
|     Then I should find "Brassica oleracea var. italica" in the app | ||||
|     And I should find "stub1.txt" in the app | ||||
|     And I should find "stub2.txt" in the app | ||||
| 
 | ||||
|     When I press "Edit entry" in the app | ||||
|     Then the field "Concept" matches value "Broccoli" in the app | ||||
|     And the field "Definition" matches value "Brassica oleracea var. italica" in the app | ||||
|     And I should find "stub1.txt" in the app | ||||
|     And I should find "stub2.txt" in the app | ||||
|     And "This entry should be automatically linked" "ion-toggle" should be selected in the app | ||||
|     And "This entry is case sensitive" "ion-toggle" should be selected in the app | ||||
|     And "Match whole words only" "ion-toggle" should be selected in the app | ||||
| @ -217,15 +234,34 @@ Feature: Test basic usage of glossary in app | ||||
|     When I set the following fields to these values in the app: | ||||
|       | Concept | Pickle | | ||||
|       | Definition | Pickle Rick | | ||||
|     And I press "Delete" within "stub2.txt" "ion-item" in the app | ||||
|     And I press "Delete" near "Are you sure you want to delete this file?" in the app | ||||
|     And I press "Add file" in the app | ||||
|     And I upload "stub3.txt" to "File" ".action-sheet-button" in the app | ||||
|     And I press "Save" in the app | ||||
|     Then I should find "Pickle Rick" in the app | ||||
|     But I should not find "Brassica oleracea var. italica" in the app | ||||
|     And I should find "stub1.txt" in the app | ||||
|     And I should find "stub3.txt" in the app | ||||
|     But I should not find "stub2.txt" in the app | ||||
|     And I should not find "Brassica oleracea var. italica" in the app | ||||
| 
 | ||||
|     When I press the back button in the app | ||||
|     Then I should find "Pickle" in the app | ||||
|     And I should find "Potato" in the app | ||||
|     But I should not find "Broccoli" in the app | ||||
| 
 | ||||
|     When I switch network connection to wifi | ||||
|     And I press "Information" in the app | ||||
|     And I press "Synchronise now" in the app | ||||
|     Then I should not find "This Glossary has offline data to be synchronised" in the app | ||||
| 
 | ||||
|     When I press "Pickle" in the app | ||||
|     Then I should find "Pickle Rick" in the app | ||||
|     And I should find "stub1.txt" in the app | ||||
|     And I should find "stub3.txt" in the app | ||||
|     But I should not find "stub2.txt" in the app | ||||
|     And I should not find "Brassica oleracea var. italica" in the app | ||||
| 
 | ||||
|   Scenario: Delete entries | ||||
|     Given I entered the glossary activity "Test glossary" on course "Course 1" as "student1" in the app | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										1
									
								
								src/addons/mod/glossary/tests/behat/fixtures/stub1.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/addons/mod/glossary/tests/behat/fixtures/stub1.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| This is a stub. | ||||
							
								
								
									
										1
									
								
								src/addons/mod/glossary/tests/behat/fixtures/stub2.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/addons/mod/glossary/tests/behat/fixtures/stub2.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| This is a stub. | ||||
							
								
								
									
										1
									
								
								src/addons/mod/glossary/tests/behat/fixtures/stub3.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/addons/mod/glossary/tests/behat/fixtures/stub3.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| This is a stub. | ||||
| @ -421,7 +421,7 @@ export class TestingBehatDomUtilsService { | ||||
|      */ | ||||
|     findElementBasedOnText( | ||||
|         locator: TestingBehatElementLocator, | ||||
|         options: TestingBehatFindOptions, | ||||
|         options: TestingBehatFindOptions = {}, | ||||
|     ): HTMLElement | undefined { | ||||
|         return this.findElementsBasedOnText(locator, options)[0]; | ||||
|     } | ||||
| @ -437,7 +437,7 @@ export class TestingBehatDomUtilsService { | ||||
|         locator: TestingBehatElementLocator, | ||||
|         options: TestingBehatFindOptions, | ||||
|     ): HTMLElement[] { | ||||
|         const topContainers = this.getCurrentTopContainerElements(options.containerName); | ||||
|         const topContainers = this.getCurrentTopContainerElements(options.containerName ?? ''); | ||||
|         let elements: HTMLElement[] = []; | ||||
| 
 | ||||
|         for (let i = 0; i < topContainers.length; i++) { | ||||
|  | ||||
| @ -361,6 +361,40 @@ export class TestingBehatRuntimeService { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get a file input id, adding it if necessary. | ||||
|      * | ||||
|      * @param locator Input locator. | ||||
|      * @returns Input id if successful, or ERROR: followed by message | ||||
|      */ | ||||
|     async getFileInputId(locator: TestingBehatElementLocator): Promise<string> { | ||||
|         this.log('Action - Upload File', { locator }); | ||||
| 
 | ||||
|         try { | ||||
|             const inputOrContainer = TestingBehatDomUtils.findElementBasedOnText(locator); | ||||
| 
 | ||||
|             if (!inputOrContainer) { | ||||
|                 return 'ERROR: No element matches input locator.'; | ||||
|             } | ||||
| 
 | ||||
|             const input = inputOrContainer.matches('input[type="file"]') | ||||
|                 ? inputOrContainer | ||||
|                 : inputOrContainer.querySelector('input[type="file"]'); | ||||
| 
 | ||||
|             if (!input) { | ||||
|                 return 'ERROR: Input element does not contain a file input.'; | ||||
|             } | ||||
| 
 | ||||
|             if (!input.hasAttribute('id')) { | ||||
|                 input.setAttribute('id', `file-${Date.now()}`); | ||||
|             } | ||||
| 
 | ||||
|             return input.getAttribute('id') ?? ''; | ||||
|         } catch (error) { | ||||
|             return 'ERROR: ' + error.message; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Trigger a pull to refresh gesture in the current page. | ||||
|      * | ||||
| @ -635,8 +669,8 @@ export type BehatTestsWindow = Window & { | ||||
| }; | ||||
| 
 | ||||
| export type TestingBehatFindOptions = { | ||||
|     containerName: string; | ||||
|     onlyClickable: boolean; | ||||
|     containerName?: string; | ||||
|     onlyClickable?: boolean; | ||||
| }; | ||||
| 
 | ||||
| export type TestingBehatElementLocator = { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user