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.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'; |     protected $windowsize = '360x720'; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @BeforeScenario |      * @BeforeScenario | ||||||
|      */ |      */ | ||||||
|     public function before_scenario(ScenarioScope $scope) { |     public function before_scenario(ScenarioScope $scope) { | ||||||
|         if (!$scope->getFeature()->hasTag('app')) { |         $feature = $scope->getFeature(); | ||||||
|  | 
 | ||||||
|  |         if (!$feature->hasTag('app')) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         global $CFG; |         $this->featurepath = dirname($feature->getFile()); | ||||||
| 
 |         $this->configure_performance_logs(); | ||||||
|         $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=/';"); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -89,6 +83,23 @@ class behat_app extends behat_app_helper { | |||||||
|         $this->enter_site(); |         $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. |      * 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. |      * Checks a field matches a certain value in the app. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -33,7 +33,7 @@ async function main() { | |||||||
|         : []; |         : []; | ||||||
| 
 | 
 | ||||||
|     if (!existsSync(pluginPath)) { |     if (!existsSync(pluginPath)) { | ||||||
|         mkdirSync(pluginPath); |         mkdirSync(pluginPath, { recursive: true }); | ||||||
|     } else { |     } else { | ||||||
|         // Empty directory, except the excluding list.
 |         // Empty directory, except the excluding list.
 | ||||||
|         const excludeFromErase = [ |         const excludeFromErase = [ | ||||||
| @ -76,21 +76,29 @@ async function main() { | |||||||
|     }; |     }; | ||||||
|     writeFileSync(pluginFilePath, replaceArguments(fileContents, replacements)); |     writeFileSync(pluginFilePath, replaceArguments(fileContents, replacements)); | ||||||
| 
 | 
 | ||||||
|     // Copy feature and snapshot files.
 |     // Copy features, snapshots, and fixtures.
 | ||||||
|     if (!excludeFeatures) { |     if (!excludeFeatures) { | ||||||
|         const behatTempFeaturesPath = `${pluginPath}/behat-tmp`; |         const behatTempFeaturesPath = `${pluginPath}/behat-tmp`; | ||||||
|         copySync(projectPath('src'), behatTempFeaturesPath, { filter: shouldCopyFileOrDirectory }); |         copySync(projectPath('src'), behatTempFeaturesPath, { filter: shouldCopyFileOrDirectory }); | ||||||
| 
 | 
 | ||||||
|         const behatFeaturesPath = `${pluginPath}/tests/behat`; |         const behatFeaturesPath = `${pluginPath}/tests/behat`; | ||||||
|         if (!existsSync(behatFeaturesPath)) { |         if (!existsSync(behatFeaturesPath)) { | ||||||
|             mkdirSync(behatFeaturesPath, {recursive: true}); |             mkdirSync(behatFeaturesPath, { recursive: true }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         for await (const file of getDirectoryFiles(behatTempFeaturesPath)) { |         for await (const file of getDirectoryFiles(behatTempFeaturesPath)) { | ||||||
|             const filePath = dirname(file); |             const filePath = dirname(file); | ||||||
|  |             const snapshotsIndex = file.indexOf('/tests/behat/snapshots/'); | ||||||
|  |             const fixturesIndex = file.indexOf('/tests/behat/fixtures/'); | ||||||
| 
 | 
 | ||||||
|             if (filePath.endsWith('/tests/behat/snapshots')) { |             if (snapshotsIndex !== -1) { | ||||||
|                 renameSync(file, behatFeaturesPath + '/snapshots/' + basename(file)); |                 moveFile(file, behatFeaturesPath + '/snapshots/' + file.slice(snapshotsIndex + 23)); | ||||||
|  | 
 | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (fixturesIndex !== -1) { | ||||||
|  |                 moveFile(file, behatFeaturesPath + '/fixtures/' + file.slice(fixturesIndex + 22)); | ||||||
| 
 | 
 | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
| @ -103,7 +111,7 @@ async function main() { | |||||||
|             const searchRegExp = /\//g; |             const searchRegExp = /\//g; | ||||||
|             const prefix = relative(behatTempFeaturesPath, newPath).replace(searchRegExp,'-') || 'core'; |             const prefix = relative(behatTempFeaturesPath, newPath).replace(searchRegExp,'-') || 'core'; | ||||||
|             const featureFilename = prefix + '-' + basename(file); |             const featureFilename = prefix + '-' + basename(file); | ||||||
|             renameSync(file, behatFeaturesPath + '/' + featureFilename); |             moveFile(file, behatFeaturesPath + '/' + featureFilename); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         rmSync(behatTempFeaturesPath, {recursive: true}); |         rmSync(behatTempFeaturesPath, {recursive: true}); | ||||||
| @ -115,7 +123,8 @@ function shouldCopyFileOrDirectory(path) { | |||||||
| 
 | 
 | ||||||
|     return stats.isDirectory() |     return stats.isDirectory() | ||||||
|         || extname(path) === '.feature' |         || extname(path) === '.feature' | ||||||
|         || extname(path) === '.png'; |         || path.includes('/tests/behat/snapshots') | ||||||
|  |         || path.includes('/tests/behat/fixtures'); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function isExcluded(file, exclusions) { | function isExcluded(file, exclusions) { | ||||||
| @ -127,6 +136,16 @@ function fail(message) { | |||||||
|     process.exit(1); |     process.exit(1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function moveFile(from, to) { | ||||||
|  |     const targetDir = dirname(to); | ||||||
|  | 
 | ||||||
|  |     if (!existsSync(targetDir)) { | ||||||
|  |         mkdirSync(targetDir, { recursive: true }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     renameSync(from, to); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function guessPluginPath() { | function guessPluginPath() { | ||||||
|     if (process.env.MOODLE_APP_BEHAT_PLUGIN_PATH) { |     if (process.env.MOODLE_APP_BEHAT_PLUGIN_PATH) { | ||||||
|         return process.env.MOODLE_APP_BEHAT_PLUGIN_PATH; |         return process.env.MOODLE_APP_BEHAT_PLUGIN_PATH; | ||||||
|  | |||||||
| @ -419,6 +419,7 @@ class AddonModGlossaryOfflineFormHandler extends AddonModGlossaryFormHandler { | |||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|     async save(glossary: AddonModGlossaryGlossary): Promise<boolean> { |     async save(glossary: AddonModGlossaryGlossary): Promise<boolean> { | ||||||
|  |         const originalData = this.page.data; | ||||||
|         const data = this.page.data; |         const data = this.page.data; | ||||||
| 
 | 
 | ||||||
|         // Upload attachments first if any.
 |         // Upload attachments first if any.
 | ||||||
| @ -428,6 +429,10 @@ class AddonModGlossaryOfflineFormHandler extends AddonModGlossaryFormHandler { | |||||||
|             offlineAttachments = await this.storeAttachments(glossary, data.timecreated); |             offlineAttachments = await this.storeAttachments(glossary, data.timecreated); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         if (originalData.concept !== data.concept) { | ||||||
|  |             await AddonModGlossaryHelper.deleteStoredFiles(glossary.id, originalData.concept, data.timecreated); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         // Save entry data.
 |         // Save entry data.
 | ||||||
|         await this.updateOfflineEntry(glossary, offlineAttachments); |         await this.updateOfflineEntry(glossary, offlineAttachments); | ||||||
| 
 | 
 | ||||||
| @ -653,8 +658,18 @@ class AddonModGlossaryOnlineFormHandler extends AddonModGlossaryFormHandler { | |||||||
|         const options = this.getSaveOptions(glossary); |         const options = this.getSaveOptions(glossary); | ||||||
|         const definition = CoreTextUtils.formatHtmlLines(data.definition); |         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.
 |         // 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' }); |         CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'glossary' }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -73,6 +73,10 @@ | |||||||
|                     [componentId]="componentId"> |                     [componentId]="componentId"> | ||||||
|                 </core-file> |                 </core-file> | ||||||
|             </div> |             </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-item class="ion-text-wrap" *ngIf="onlineEntry && tagsEnabled && entry && onlineEntry.tags && onlineEntry.tags.length > 0"> | ||||||
|                 <ion-label> |                 <ion-label> | ||||||
|                     <div slot="start">{{ 'core.tag.tags' | translate }}:</div> |                     <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 { CoreComments } from '@features/comments/services/comments'; | ||||||
| import { CoreRatingInfo } from '@features/rating/services/rating'; | import { CoreRatingInfo } from '@features/rating/services/rating'; | ||||||
| import { CoreTag } from '@features/tag/services/tag'; | import { CoreTag } from '@features/tag/services/tag'; | ||||||
|  | import { FileEntry } from '@ionic-native/file/ngx'; | ||||||
| import { IonRefresher } from '@ionic/angular'; | import { IonRefresher } from '@ionic/angular'; | ||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
| import { CoreNetwork } from '@services/network'; | import { CoreNetwork } from '@services/network'; | ||||||
| @ -54,6 +55,7 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy { | |||||||
|     componentId?: number; |     componentId?: number; | ||||||
|     onlineEntry?: AddonModGlossaryEntry; |     onlineEntry?: AddonModGlossaryEntry; | ||||||
|     offlineEntry?: AddonModGlossaryOfflineEntry; |     offlineEntry?: AddonModGlossaryOfflineEntry; | ||||||
|  |     offlineEntryFiles?: FileEntry[]; | ||||||
|     entries!: AddonModGlossaryEntryEntriesSwipeManager; |     entries!: AddonModGlossaryEntryEntriesSwipeManager; | ||||||
|     glossary?: AddonModGlossaryGlossary; |     glossary?: AddonModGlossaryGlossary; | ||||||
|     entryUpdatedObserver?: CoreEventObserver; |     entryUpdatedObserver?: CoreEventObserver; | ||||||
| @ -263,6 +265,13 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy { | |||||||
|             const glossary = await this.loadGlossary(); |             const glossary = await this.loadGlossary(); | ||||||
| 
 | 
 | ||||||
|             this.offlineEntry = await AddonModGlossaryOffline.getOfflineEntry(glossary.id, timecreated); |             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.canEdit = true; | ||||||
|             this.canDelete = true; |             this.canDelete = true; | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|  | |||||||
| @ -930,6 +930,7 @@ export class AddonModGlossaryProvider { | |||||||
|      * @param concept Glossary entry concept. |      * @param concept Glossary entry concept. | ||||||
|      * @param definition Glossary entry concept definition. |      * @param definition Glossary entry concept definition. | ||||||
|      * @param options Options for the entry. |      * @param options Options for the entry. | ||||||
|  |      * @param attachId Attachments ID (if any attachment). | ||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      */ |      */ | ||||||
|     async updateEntry( |     async updateEntry( | ||||||
| @ -938,6 +939,7 @@ export class AddonModGlossaryProvider { | |||||||
|         concept: string, |         concept: string, | ||||||
|         definition: string, |         definition: string, | ||||||
|         options?: Record<string, AddonModGlossaryEntryOption>, |         options?: Record<string, AddonModGlossaryEntryOption>, | ||||||
|  |         attachId?: number, | ||||||
|         siteId?: string, |         siteId?: string, | ||||||
|     ): Promise<void> { |     ): Promise<void> { | ||||||
|         const site = await CoreSites.getSite(siteId); |         const site = await CoreSites.getSite(siteId); | ||||||
| @ -950,6 +952,13 @@ export class AddonModGlossaryProvider { | |||||||
|             options: CoreUtils.objectToArrayOfObjects(options || {}, 'name', 'value'), |             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); |         const response = await site.write<AddonModGlossaryUpdateEntryWSResponse>('mod_glossary_update_entry', params); | ||||||
| 
 | 
 | ||||||
|         if (!response.result) { |         if (!response.result) { | ||||||
|  | |||||||
| @ -154,42 +154,51 @@ Feature: Test basic usage of glossary in app | |||||||
|     Then I should find "Garlic" in the app |     Then I should find "Garlic" in the app | ||||||
|     And I should find "Allium sativum" 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 |     Given I entered the glossary activity "Test glossary" on course "Course 1" as "student1" in the app | ||||||
| 
 | 
 | ||||||
|     # Online |     # Online | ||||||
|     When I press "Add a new entry" in the app |     When I press "Cucumber" 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 |  | ||||||
|     And I press "Edit entry" in the app |     And I press "Edit entry" in the app | ||||||
|     Then the field "Concept" matches value "Cashew" in the app |     Then the field "Concept" matches value "Cucumber" in the app | ||||||
|     And the field "Definition" matches value "Cashew is a fruit" 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: |     When I set the following fields to these values in the app: | ||||||
|       | Concept | Coconut | |       | Concept | Coconut | | ||||||
|       | Definition | Coconut is a fruit | |       | 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 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 "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 "Match whole words only" "ion-toggle" in the app | ||||||
|     And I press "Save" in the app |     And I press "Save" in the app | ||||||
|     Then I should find "Coconut is a fruit" 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 |     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 "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 |     And "Match whole words only" "ion-toggle" should be selected in the app | ||||||
| 
 | 
 | ||||||
|     When I press "Save" in the app |     When I press "Delete" within "stub2.txt" "ion-item" in the app | ||||||
|     And I press the back button 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 |     Then I should find "Coconut" in the app | ||||||
|     And I should find "Potato" 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 |     # Offline | ||||||
|     When I press "Add a new entry" in the app |     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: |     And I set the following fields to these values in the app: | ||||||
|       | Concept | Broccoli | |       | Concept | Broccoli | | ||||||
|       | Definition | Brassica oleracea var. italica | |       | 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 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 "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 "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 |     When I press "Broccoli" in the app | ||||||
|     Then I should find "Brassica oleracea var. italica" 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 |     When I press "Edit entry" in the app | ||||||
|     Then the field "Concept" matches value "Broccoli" 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 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 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 "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 |     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: |     When I set the following fields to these values in the app: | ||||||
|       | Concept | Pickle | |       | Concept | Pickle | | ||||||
|       | Definition | Pickle Rick | |       | 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 |     And I press "Save" in the app | ||||||
|     Then I should find "Pickle Rick" 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 |     When I press the back button in the app | ||||||
|     Then I should find "Pickle" in the app |     Then I should find "Pickle" in the app | ||||||
|     And I should find "Potato" in the app |     And I should find "Potato" in the app | ||||||
|     But I should not find "Broccoli" 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 |   Scenario: Delete entries | ||||||
|     Given I entered the glossary activity "Test glossary" on course "Course 1" as "student1" in the app |     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( |     findElementBasedOnText( | ||||||
|         locator: TestingBehatElementLocator, |         locator: TestingBehatElementLocator, | ||||||
|         options: TestingBehatFindOptions, |         options: TestingBehatFindOptions = {}, | ||||||
|     ): HTMLElement | undefined { |     ): HTMLElement | undefined { | ||||||
|         return this.findElementsBasedOnText(locator, options)[0]; |         return this.findElementsBasedOnText(locator, options)[0]; | ||||||
|     } |     } | ||||||
| @ -437,7 +437,7 @@ export class TestingBehatDomUtilsService { | |||||||
|         locator: TestingBehatElementLocator, |         locator: TestingBehatElementLocator, | ||||||
|         options: TestingBehatFindOptions, |         options: TestingBehatFindOptions, | ||||||
|     ): HTMLElement[] { |     ): HTMLElement[] { | ||||||
|         const topContainers = this.getCurrentTopContainerElements(options.containerName); |         const topContainers = this.getCurrentTopContainerElements(options.containerName ?? ''); | ||||||
|         let elements: HTMLElement[] = []; |         let elements: HTMLElement[] = []; | ||||||
| 
 | 
 | ||||||
|         for (let i = 0; i < topContainers.length; i++) { |         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. |      * Trigger a pull to refresh gesture in the current page. | ||||||
|      * |      * | ||||||
| @ -635,8 +669,8 @@ export type BehatTestsWindow = Window & { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export type TestingBehatFindOptions = { | export type TestingBehatFindOptions = { | ||||||
|     containerName: string; |     containerName?: string; | ||||||
|     onlyClickable: boolean; |     onlyClickable?: boolean; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export type TestingBehatElementLocator = { | export type TestingBehatElementLocator = { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user