From 8b41f53e141fc3f5eda067f5671f320a585c2dc9 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 21 Mar 2022 13:12:30 +0700 Subject: [PATCH] New TestControl API, rename testControl.ts to testSetup New API entries in testSetup.ts: * initTestProject to create new, empty project * addCustomField to add a custom field to a project * addLexEntry to add a lexical entry to a project * getProjectJson for help with logging or expect()ing project config * changePassword API moved from testControl.ts to testSetup.ts Also added some examples in example.spec.ts showing how to use the testSetup API. Each example is in a `test.skip` block so that they will not actually run by default. --- test/e2e/change-password.spec.ts | 2 +- test/e2e/example.spec.ts | 46 +++++++---- test/e2e/tsconfig.json | 1 + test/e2e/utils/TestControl.php | 129 +++++++++++++++++++++++++++++-- test/e2e/utils/testControl.ts | 6 -- test/e2e/utils/testSetup.ts | 51 ++++++++++++ 6 files changed, 206 insertions(+), 29 deletions(-) delete mode 100644 test/e2e/utils/testControl.ts create mode 100644 test/e2e/utils/testSetup.ts diff --git a/test/e2e/change-password.spec.ts b/test/e2e/change-password.spec.ts index 8d7a634e91..34a624662e 100644 --- a/test/e2e/change-password.spec.ts +++ b/test/e2e/change-password.spec.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; import { test } from './utils/fixtures'; import { ChangePasswordPage } from './pages/change-password.page'; -import { changePassword } from './utils/testControl'; +import { changePassword } from './utils/testSetup'; import { LoginPage } from './pages/login.page'; import { PageHeader } from './pages/page-header.page'; diff --git a/test/e2e/example.spec.ts b/test/e2e/example.spec.ts index c0844a4680..26618cef16 100644 --- a/test/e2e/example.spec.ts +++ b/test/e2e/example.spec.ts @@ -1,24 +1,38 @@ -import type { APIRequestContext } from '@playwright/test'; -import { expect } from '@playwright/test'; import constants from './testConstants.json'; -import { testControl } from './utils/jsonrpc'; -import type { UserTab } from './utils/fixtures'; import { test } from './utils/fixtures'; +import { addCustomField, addLexEntry, initTestProject } from './utils/testSetup'; -test('API call', async ({ request }: { request: APIRequestContext }) => { - const result = await testControl(request, 'check_test_api'); - expect(result).toBeDefined(); - expect(result).toHaveProperty('api_is_working'); - expect(result.api_is_working).toBeTruthy(); +test.skip('Reset project', async ({ request }) => { + await initTestProject(request, + constants.testProjectCode, + constants.testProjectName, + constants.adminUsername, + ); }); -test('Reset project', async ({ request, adminTab }: { request: APIRequestContext, adminTab: UserTab }) => { - const result = await testControl(request, 'init_test_project', [ +test.skip('Reset project and add test data', async ({ request }) => { + await initTestProject(request, constants.testProjectCode, constants.testProjectName, - constants.adminUsername, - ]); - await adminTab.goto('/app/projects'); - await expect(adminTab.locator(`[data-ng-repeat="project in visibleProjects"] a:has-text("${constants.testProjectName}")`)).toBeVisible(); - // await adminTab.screenshot({ path: 'post-login.png' }); + constants.managerUsername, + ); + const customFieldName = await addCustomField(request, + constants.testProjectCode, + 'CustomField', + 'entry', + 'MultiString', + {inputSystems: ['th']} + ); + // Lexical entry from testConstants.json with no changes + await addLexEntry(request, constants.testProjectCode, constants.testEntry1); + // Example of adding data in the custom field + const data = { + ...constants.testEntry2, + customFields: { + [customFieldName]: { th: { value: 'contents of custom field' } } + } + }; + // The [customFieldName] syntax is how you can assign a property without knowing it at compile-time + // console.log(data); // Uncomment this to see the data you're adding + await addLexEntry(request, constants.testProjectCode, data); }); diff --git a/test/e2e/tsconfig.json b/test/e2e/tsconfig.json index 32643dc11f..62b5a02a0d 100644 --- a/test/e2e/tsconfig.json +++ b/test/e2e/tsconfig.json @@ -1,4 +1,5 @@ { + "extends": "../../tsconfig.json", "buildOnSave": false, "compileOnSave": false, "compilerOptions": { diff --git a/test/e2e/utils/TestControl.php b/test/e2e/utils/TestControl.php index 5f3512c71a..6e115be81d 100644 --- a/test/e2e/utils/TestControl.php +++ b/test/e2e/utils/TestControl.php @@ -2,15 +2,27 @@ namespace Api\Service; use Api\Model\Shared\Mapper\MongoStore; +use Api\Model\Shared\Mapper\ArrayOf; use Api\Model\Shared\Command\UserCommands; use Api\Model\Shared\ProjectModel; use Api\Model\Shared\UserModel; use Api\Model\Shared\UserModelWithPassword; +use Api\Model\Languageforge\Lexicon\LexEntryModel; use Api\Model\Languageforge\Lexicon\LexProjectModel; +use Api\Model\Languageforge\Lexicon\Config\LexConfig; +use Api\Model\Languageforge\Lexicon\Config\LexConfigOptionList; +use Api\Model\Languageforge\Lexicon\Config\LexConfigMultiOptionList; +use Api\Model\Languageforge\Lexicon\Config\LexConfigMultiParagraph; +use Api\Model\Languageforge\Lexicon\Config\LexConfigMultiText; +use Api\Model\Languageforge\Lexicon\Config\LexRoleViewConfig; +use Api\Model\Languageforge\Lexicon\Config\LexUserViewConfig; +use Api\Model\Languageforge\Lexicon\Config\LexViewFieldConfig; +use Api\Model\Languageforge\Lexicon\Config\LexViewMultiTextFieldConfig; use Api\Model\Shared\Rights\ProjectRoles; use Api\Model\Shared\Rights\SystemRoles; use Api\Model\Shared\Mapper\IdReference; - +use Api\Model\Languageforge\Lexicon\Command\LexEntryDecoder; +use Api\Model\Languageforge\Lexicon\Command\LexProjectCommands; // use MongoDB\Client; use Api\Library\Shared\Website; @@ -99,7 +111,15 @@ public function change_password($username, $password) return ''; } - public function init_test_project($projectCode = null, $projectName = null, $ownerUsername = null) + public function reset_projects() + { + $db = MongoStore::connect(DATABASE); + $db->dropCollection('projects'); + $db->createCollection('projects'); + return true; + } + + public function init_test_project($projectCode = null, $projectName = null, $ownerUsername = null, $memberUsernames = []) { if (! $projectCode) { $projectCode = 'test_project'; @@ -108,7 +128,6 @@ public function init_test_project($projectCode = null, $projectName = null, $own $projectName = 'Test Project'; } - // TODO: Handle this with MongoStore instead of through Commands library $owner = new UserModel(); $ownerId = ''; if ($owner->readByUserName($ownerUsername)) { @@ -117,11 +136,9 @@ public function init_test_project($projectCode = null, $projectName = null, $own $ownerId = $this->create_user($ownerUsername); } - $db = MongoStore::connect(DATABASE); - $db->dropCollection('projects'); - $db->createCollection('projects'); $coll = $db->selectCollection('projects'); + $coll->deleteOne([ 'projectCode' => $projectCode ]); $projectModel = new ProjectModel(); $projectModel->projectName = $projectName; $projectModel->projectCode = $projectCode; @@ -129,9 +146,109 @@ public function init_test_project($projectCode = null, $projectName = null, $own $projectModel->siteName = $this->website->domain; $projectModel->ownerRef = new IdReference($ownerId); $projectModel->addUser($ownerId, ProjectRoles::MANAGER); + foreach ($memberUsernames as $username) { + $user = new UserModel(); + if ($user->readByUserName($username)) { + $userId = $user->id->asString(); + $projectModel->addUser($userId, ProjectRoles::CONTRIBUTOR); + } + } MongoStore::dropAllCollections($projectModel->databaseName()); MongoStore::dropDB($projectModel->databaseName()); $projectModel->write(); return $projectModel->id->asString(); } + + public function add_custom_field(string $projectCode, string $customFieldName, string $parentField = 'entry', string $customFieldType = 'MultiString', $extraOptions = null) + { + error_log('add_custom_field'); + $prefix = 'customField_' . $parentField . '_'; + if (\strpos($customFieldName, $prefix) !== 0) { + $customFieldName = $prefix . $customFieldName; + } + $project = ProjectModel::getByProjectCode($projectCode); + error_log($project->id->asString()); + switch($parentField) { + case 'entry': $config = $project->config->entry; break; + case 'senses': $config = $project->config->entry->fields[LexConfig::SENSES_LIST]; break; + case 'examples': $config = $project->config->entry->fields[LexConfig::SENSES_LIST]->fields[LexConfig::EXAMPLES_LIST]; break; + } + $config->fieldOrder->ensureValueExists($customFieldName); + if (! array_key_exists($customFieldName, $config->fields)) { + switch($customFieldType) { + case "ReferenceAtom": + $config->fields[$customFieldName] = new LexConfigOptionList(); + $config->fields[$customFieldName]->listCode = $extraOptions['listCode']; + break; + case "ReferenceCollection": + $config->fields[$customFieldName] = new LexConfigMultiOptionList(); + $config->fields[$customFieldName]->listCode = $extraOptions['listCode']; + break; + case "OwningAtom": + $config->fields[$customFieldName] = new LexConfigMultiParagraph(); + break; + default: + $config->fields[$customFieldName] = new LexConfigMultiText(); + $config->fields[$customFieldName]->inputSystems = new ArrayOf(); + if ($extraOptions['inputSystems']) { + foreach ($extraOptions['inputSystems'] as $ws) { + $config->fields[$customFieldName]->inputSystems->ensureValueExists($ws); + } + } + }; + $label = str_replace($prefix, '', $customFieldName); + $config->fields[$customFieldName]->label = str_replace(' ', '_', $label); + $config->fields[$customFieldName]->hideIfEmpty = false; + } + // PHP copies objects by value, not reference, so now we have to write the config back + switch($parentField) { + case 'entry': $project->config->entry = $config; break; + case 'senses': $project->config->entry->fields[LexConfig::SENSES_LIST] = $config; break; + case 'examples': $project->config->entry->fields[LexConfig::SENSES_LIST]->fields[LexConfig::EXAMPLES_LIST] = $config; break; + } + + // Now make the custom field visible in all views + foreach ($project->config->roleViews as $role => $roleView) { + if (!array_key_exists($customFieldName, $roleView->fields)) { + if ($customFieldType == 'MultiUnicode' || $customFieldType == 'MultiString') { + $roleView->fields[$customFieldName] = new LexViewMultiTextFieldConfig(); + } else { + $roleView->fields[$customFieldName] = new LexViewFieldConfig(); + } + $roleView->fields[$customFieldName]->show = true; + } + } + foreach ($project->config->userViews as $userId => $userView) { + if (!array_key_exists($customFieldName, $userView->fields)) { + if ($customFieldType == 'MultiUnicode' || $customFieldType == 'MultiString') { + $userView->fields[$customFieldName] = new LexViewMultiTextFieldConfig(); + } else { + $userView->fields[$customFieldName] = new LexViewFieldConfig(); + } + $userView->fields[$customFieldName]->show = true; + } + } + + $project->write(); + + return $customFieldName; + } + + public function add_lexical_entry(string $projectCode, array $data) + { + $project = ProjectModel::getByProjectCode($projectCode); + $entry = new LexEntryModel($project); + LexEntryDecoder::decode($entry, $data); + return $entry->write(); + } + + public function get_project_json(string $projectCode) { + $db = MongoStore::connect(DATABASE); + $project = $db->projects->findOne(['projectCode' => $projectCode]); + return $project; + } + + public function new_method() { + return 'hello'; + } } diff --git a/test/e2e/utils/testControl.ts b/test/e2e/utils/testControl.ts deleted file mode 100644 index 5d08ab736c..0000000000 --- a/test/e2e/utils/testControl.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { APIRequestContext } from '@playwright/test'; -import { testControl } from './jsonrpc'; - -export function changePassword(request: APIRequestContext, username: string, password: string) { - return testControl(request, 'change_password', [username, password]); -} diff --git a/test/e2e/utils/testSetup.ts b/test/e2e/utils/testSetup.ts new file mode 100644 index 0000000000..fb1ab850a9 --- /dev/null +++ b/test/e2e/utils/testSetup.ts @@ -0,0 +1,51 @@ +import { testControl } from './jsonrpc'; +import { APIRequestContext } from '@playwright/test'; + +type CustomFieldType = + 'MultiString' | + 'ReferenceAtom' | + 'ReferenceCollection' | + 'OwningAtom' + // TODO: Add more (look at LfMerge custom field code to find out what they can be) +; + +type LfFieldType = + 'fields' | + 'multitext' | + 'multiparagraph' | + 'optionlist' | + 'multioptionlist' | + 'pictures' +; + +export function initTestProject(request: APIRequestContext, + projectCode: string, + projectName: string, + ownerUsername: string, + memberUsernames: string[] = []) +{ + return testControl(request, 'init_test_project', [projectCode, projectName, ownerUsername, memberUsernames]); +} + +export function addCustomField(request: APIRequestContext, + projectCode: string, + fieldName: string, + parentField: 'entry' | 'senses' | 'examples', + fieldType: CustomFieldType = 'MultiString', + extraOptions: any = null) { + return testControl(request, 'add_custom_field', [projectCode, fieldName, parentField, fieldType, extraOptions]); +} + +export function getProjectJson(request: APIRequestContext, + projectCode: string) { + return testControl(request, 'get_project_json', [projectCode]); +} + +export function changePassword(request: APIRequestContext, username: string, password: string) { + return testControl(request, 'change_password', [username, password]); +} + +export function addLexEntry(request: APIRequestContext, projectCode: string, data: any) { + if (data.id == null) data.id = ''; + return testControl(request, 'add_lexical_entry', [projectCode, data]); +}