MOBILE-2652 glossary: Edit attachments
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
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
This is a stub.
|
|
@ -0,0 +1 @@
|
||||||
|
This is a stub.
|
|
@ -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…
Reference in New Issue