123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619 |
- <?php
- /*
- * @file
- * Contains tests for the default user interface.
- */
- /**
- * Test the UI of tmgmt, for example the checkout form.
- */
- class TMGMTUITestCase extends TMGMTBaseTestCase {
- static function getInfo() {
- return array(
- 'name' => 'UI tests',
- 'description' => 'Verifies basic functionality of the user interface',
- 'group' => 'Translation Management',
- );
- }
- function setUp() {
- parent::setup(array('tmgmt_ui'));
- parent::createLanguagesLoginTranslator();
- }
- /**
- * Test the page callbacks to create jobs and check them out.
- */
- function testCheckoutForm() {
- // Add a first item to the job. This will auto-create the job.
- $job = tmgmt_job_match_item('en', '');
- $job->addItem('test_source', 'test', 1);
- // Go to checkout form.
- $redirects = tmgmt_ui_job_checkout_multiple(array($job));
- $this->drupalGet(reset($redirects));
- // Check checkout form.
- $this->assertText('test_source:test:1');
- // Add two more job items.
- $job->addItem('test_source', 'test', 2);
- $job->addItem('test_source', 'test', 3);
- // Go to checkout form.
- $redirects = tmgmt_ui_job_checkout_multiple(array($job));
- $this->drupalGet(reset($redirects));
- // Check checkout form.
- $this->assertText('test_source:test:1');
- $this->assertText('test_source:test:2');
- $this->assertText('test_source:test:3');
- // @todo: Test ajax functionality.
- // Attempt to translate into greek.
- $edit = array(
- 'target_language' => 'el',
- 'settings[action]' => 'translate',
- );
- $this->drupalPost(NULL, $edit, t('Submit to translator'));
- $this->assertText(t('@translator can not translate from @source to @target.', array('@translator' => 'Test translator (auto created)', '@source' => 'English', '@target' => 'Greek')));
- // Job still needs to be in state new.
- $job = entity_load_unchanged('tmgmt_job', $job->tjid);
- $this->assertTrue($job->isUnprocessed());
- $edit = array(
- 'target_language' => 'es',
- 'settings[action]' => 'translate',
- );
- $this->drupalPost(NULL, $edit, t('Submit to translator'));
- // Job needs to be in state active.
- $job = entity_load_unchanged('tmgmt_job', $job->tjid);
- $this->assertTrue($job->isActive());
- foreach ($job->getItems() as $job_item) {
- /* @var $job_item TMGMTJobItem */
- $this->assertTrue($job_item->isNeedsReview());
- }
- $this->assertText(t('Test translation created'));
- $this->assertNoText(t('Test translator called'));
- // Test redirection.
- $this->assertText(t('Job overview'));
- // Another job.
- $previous_tjid = $job->tjid;
- $job = tmgmt_job_match_item('en', '');
- $job->addItem('test_source', 'test', 1);
- $this->assertNotEqual($job->tjid, $previous_tjid);
- // Go to checkout form.
- $redirects = tmgmt_ui_job_checkout_multiple(array($job));
- $this->drupalGet(reset($redirects));
- // Check checkout form.
- $this->assertText('You can provide a label for this job in order to identify it easily later on.');
- $this->assertText('test_source:test:1');
- $edit = array(
- 'target_language' => 'es',
- 'settings[action]' => 'submit',
- );
- $this->drupalPost(NULL, $edit, t('Submit to translator'));
- $this->assertText(t('Test submit'));
- $job = entity_load_unchanged('tmgmt_job', $job->tjid);
- $this->assertTrue($job->isActive());
- // Another job.
- $job = tmgmt_job_match_item('en', 'es');
- $job->addItem('test_source', 'test', 1);
- // Go to checkout form.
- $redirects = tmgmt_ui_job_checkout_multiple(array($job));
- $this->drupalGet(reset($redirects));
- // Check checkout form.
- $this->assertText('You can provide a label for this job in order to identify it easily later on.');
- $this->assertText('test_source:test:1');
- $edit = array(
- 'settings[action]' => 'reject',
- );
- $this->drupalPost(NULL, $edit, t('Submit to translator'));
- $this->assertText(t('This is not supported'));
- $job = entity_load_unchanged('tmgmt_job', $job->tjid);
- $this->assertTrue($job->isRejected());
- // Check displayed job messages.
- $args = array('@view' => 'view-tmgmt-ui-job-messages');
- $this->assertEqual(2, count($this->xpath('//div[contains(@class, @view)]//tbody/tr', $args)));
- // Check that the author for each is the current user.
- $message_authors = $this->xpath('////div[contains(@class, @view)]//td[contains(@class, @field)]/span', $args + array('@field' => 'views-field-name'));
- $this->assertEqual(2, count($message_authors));
- foreach ($message_authors as $message_author) {
- $this->assertEqual((string)$message_author, $this->translator_user->name);
- }
- // Make sure that rejected jobs can be re-submitted.
- $this->assertTrue($job->isSubmittable());
- $edit = array(
- 'settings[action]' => 'translate',
- );
- $this->drupalPost(NULL, $edit, t('Submit to translator'));
- $this->assertText(t('Test translation created'));
- // Another job.
- $job = tmgmt_job_match_item('en', 'es');
- $job->addItem('test_source', 'test', 1);
- // Go to checkout form.
- $redirects = tmgmt_ui_job_checkout_multiple(array($job));
- $this->drupalGet(reset($redirects));
- // Check checkout form.
- $this->assertText('You can provide a label for this job in order to identify it easily later on.');
- $this->assertText('test_source:test:1');
- $edit = array(
- 'settings[action]' => 'fail',
- );
- $this->drupalPost(NULL, $edit, t('Submit to translator'));
- $this->assertText(t('Service not reachable'));
- $job = entity_load_unchanged('tmgmt_job', $job->tjid);
- $this->assertTrue($job->isUnprocessed());
- // Verify that we are still on the form.
- $this->assertText('You can provide a label for this job in order to identify it easily later on.');
- // Another job.
- $job = tmgmt_job_match_item('en', 'es');
- $job->addItem('test_source', 'test', 1);
- // Go to checkout form.
- $redirects = tmgmt_ui_job_checkout_multiple(array($job));
- $this->drupalGet(reset($redirects));
- // Check checkout form.
- $this->assertText('You can provide a label for this job in order to identify it easily later on.');
- $this->assertText('test_source:test:1');
- $edit = array(
- 'settings[action]' => 'not_translatable',
- );
- $this->drupalPost(NULL, $edit, t('Submit to translator'));
- // @todo Update to correct failure message.
- $this->assertText(t('Fail'));
- $job = entity_load_unchanged('tmgmt_job', $job->tjid);
- $this->assertTrue($job->isUnprocessed());
- // Test default settings.
- $this->default_translator->settings['action'] = 'reject';
- $this->default_translator->save();
- $job = tmgmt_job_match_item('en', 'es');
- $job->addItem('test_source', 'test', 1);
- // Go to checkout form.
- $redirects = tmgmt_ui_job_checkout_multiple(array($job));
- $this->drupalGet(reset($redirects));
- // Check checkout form.
- $this->assertText('You can provide a label for this job in order to identify it easily later on.');
- $this->assertText('test_source:test:1');
- // The action should now default to reject.
- $this->drupalPost(NULL, array(), t('Submit to translator'));
- $this->assertText(t('This is not supported.'));
- $job = entity_load_unchanged('tmgmt_job', $job->tjid);
- $this->assertTrue($job->isRejected());
- }
- /**
- * Tests the tmgmt_ui_job_checkout() function.
- */
- function testCheckoutFunction() {
- $job = $this->createJob();
- // Check out a job when only the test translator is available. That one has
- // settings, so a checkout is necessary.
- $redirects = tmgmt_ui_job_checkout_multiple(array($job));
- $uri = $job->uri();
- $this->assertEqual($uri['path'], $redirects[0]);
- $this->assertTrue($job->isUnprocessed());
- $job->delete();
- // Hide settings on the test translator.
- $default_translator = tmgmt_translator_load('test_translator');
- $default_translator->settings = array(
- 'expose_settings' => FALSE,
- );
- $job = $this->createJob();
- $redirects = tmgmt_ui_job_checkout_multiple(array($job));
- $this->assertFalse($redirects);
- $this->assertTrue($job->isActive());
- // A job without target language needs to be checked out.
- $job = $this->createJob('en', '');
- $redirects = tmgmt_ui_job_checkout_multiple(array($job));
- $uri = $job->uri();
- $this->assertEqual($uri['path'], $redirects[0]);
- $this->assertTrue($job->isUnprocessed());
- // Create a second file translator. This should check
- // out immediately.
- $job = $this->createJob();
- $second_translator = $this->createTranslator();
- $second_translator->settings = array(
- 'expose_settings' => FALSE,
- );
- $second_translator->save();
- $redirects = tmgmt_ui_job_checkout_multiple(array($job));
- $uri = $job->uri();
- $this->assertEqual($uri['path'], $redirects[0]);
- $this->assertTrue($job->isUnprocessed());
- }
- /**
- * Tests of the job item review process.
- */
- public function testReview() {
- $job = $this->createJob();
- $job->translator = $this->default_translator->name;
- $job->settings = array();
- $job->save();
- $item = $job->addItem('test_source', 'test', 1);
- $data = tmgmt_flatten_data($item->getData());
- $keys = array_keys($data);
- $key = $keys[0];
- $this->drupalGet('admin/tmgmt/items/' . $item->tjiid);
- // Testing the result of the
- // TMGMTTranslatorUIControllerInterface::reviewDataItemElement()
- $this->assertText(t('Testing output of review data item element @key from the testing translator.', array('@key' => $key)));
- // Test the review tool source textarea.
- $this->assertFieldByName('dummy|deep_nesting[source]', $data[$key]['#text']);
- // Save translation.
- $this->drupalPost(NULL, array('dummy|deep_nesting[translation]' => $data[$key]['#text'] . 'translated'), t('Save'));
- $this->drupalGet('admin/tmgmt/items/' . $item->tjiid);
- // Check if translation has been saved.
- $this->assertFieldByName('dummy|deep_nesting[translation]', $data[$key]['#text'] . 'translated');
- }
- /**
- * Tests the UI of suggestions.
- */
- public function testSuggestions() {
- // Prepare a job and a node for testing.
- $job = $this->createJob();
- $job->addItem('test_source', 'test', 1);
- $job->addItem('test_source', 'test', 7);
- // Go to checkout form.
- $redirects = tmgmt_ui_job_checkout_multiple(array($job));
- $this->drupalGet(reset($redirects));
- $this->assertRaw('20');
- // Load all suggestions.
- $commands = $this->drupalPostAJAX(NULL, array(), array('op' => t('Load suggestions')));
- $this->assertEqual(count($commands), 4, 'Found 4 commands in AJAX-Request.');
- // Check each command for success.
- foreach ($commands as $command) {
- // No checks against the settings because we not use ajax to save.
- if ($command['command'] == 'settings') {
- }
- // Other commands must be from type "insert".
- else if ($command['command'] == 'insert') {
- // This should be the tableselect javascript file for the header.
- if (($command['method'] == 'prepend') && ($command['selector'] == 'head')) {
- $this->assertTrue(substr_count($command['data'], 'misc/tableselect.js'), 'Javascript for Tableselect found.');
- }
- // Check for the main content, the tableselect with the suggestions.
- else if (($command['method'] == NULL) && ($command['selector'] == NULL)) {
- $this->assertTrue(substr_count($command['data'], '</th>') == 5, 'Found five table header.');
- $this->assertTrue(substr_count($command['data'], '</tr>') == 3, 'Found two suggestion and one table header.');
- $this->assertTrue(substr_count($command['data'], '<td>11</td>') == 2, 'Found 10 words to translate per suggestion.');
- $this->assertTrue(substr_count($command['data'], 'value="Add suggestions"'), 'Found add button.');
- }
- // Nothing to prepend...
- else if (($command['method'] == 'prepend') && ($command['selector'] == NULL)) {
- $this->assertTrue(empty($command['data']), 'No content will be prepended.');
- }
- else {
- $this->fail('Unknown method/selector combination.');
- debug($command);
- }
- }
- else {
- $this->fail('Unknown command.');
- debug($command);
- }
- }
- $this->assertText('test_source:test_suggestion:1');
- $this->assertText('test_source:test_suggestion:7');
- $this->assertText('Test suggestion for test source 1');
- $this->assertText('Test suggestion for test source 7');
- // Add the second suggestion.
- $edit = array('suggestions_table[2]' => TRUE);
- $this->drupalPost(NULL, $edit, t('Add suggestions'));
- // Total word count should now include the added job.
- $this->assertRaw('31');
- // The suggestion for 7 was added, so there should now be a suggestion
- // or the suggestion instead.
- $this->assertNoText('Test suggestion for test source 7');
- $this->assertText('test_source:test_suggestion_suggestion:7');
- }
- /**
- * Test the process of aborting and resubmitting the job.
- */
- function testAbortJob() {
- $job = $this->createJob();
- $job->addItem('test_source', 'test', 1);
- $job->addItem('test_source', 'test', 2);
- $job->addItem('test_source', 'test', 3);
- $edit = array(
- 'target_language' => 'es',
- 'settings[action]' => 'translate',
- );
- $this->drupalPost('admin/tmgmt/jobs/' . $job->tjid, $edit, t('Submit to translator'));
- // Abort job.
- $this->drupalPost('admin/tmgmt/jobs/' . $job->tjid, array(), t('Abort job'));
- $this->drupalPost(NULL, array(), t('Confirm'));
- // Reload job and check its state.
- entity_get_controller('tmgmt_job')->resetCache();
- /** @var TMGMTJob $job */
- $job = tmgmt_job_load($job->tjid);
- $this->assertTrue($job->isAborted());
- foreach ($job->getItems() as $item) {
- $this->assertTrue($item->isAborted());
- }
- // Resubmit the job.
- $this->drupalPost('admin/tmgmt/jobs/' . $job->tjid, array(), t('Resubmit'));
- $this->drupalPost(NULL, array(), t('Confirm'));
- // Test for the log message.
- $this->assertRaw(t('This job is a duplicate of the previously aborted job <a href="@url">#@id</a>',
- array('@url' => url('admin/tmgmt/jobs/' . $job->tjid), '@id' => $job->tjid)));
- // Load the resubmitted job and check for its status and values.
- $url_parts = explode('/', $this->getUrl());
- $resubmitted_job = tmgmt_job_load(array_pop($url_parts));
- $this->assertTrue($resubmitted_job->isUnprocessed());
- $this->assertEqual($job->translator, $resubmitted_job->translator);
- $this->assertEqual($job->source_language, $resubmitted_job->source_language);
- $this->assertEqual($job->target_language, $resubmitted_job->target_language);
- $this->assertEqual($job->settings, $resubmitted_job->settings);
- // Test if job items were duplicated correctly.
- /** @var TMGMTJobItem $item */
- foreach ($job->getItems() as $item) {
- // We match job items based on "id #" string. This is not that straight
- // forward, but it works as the test source text is generated as follows:
- // Text for job item with type #type and id #id.
- $_items = $resubmitted_job->getItems(array('data' => array('value' => '%id ' . $item->item_id . '%', 'operator' => 'LIKE')));
- $_item = reset($_items);
- /** @var TMGMTJobItem $_item */
- $this->assertNotEqual($_item->tjid, $item->tjid);
- $this->assertEqual($_item->plugin, $item->plugin);
- $this->assertEqual($_item->item_id, $item->item_id);
- $this->assertEqual($_item->item_type, $item->item_type);
- // Make sure counts have been recalculated.
- $this->assertTrue($_item->word_count > 0);
- $this->assertTrue($_item->count_pending > 0);
- $this->assertEqual($_item->count_translated, 0);
- $this->assertEqual($_item->count_accepted, 0);
- $this->assertEqual($_item->count_reviewed, 0);
- }
- // Navigate back to the aborted job and check for the log message.
- $this->drupalGet('admin/tmgmt/jobs/' . $job->tjid);
- $this->assertRaw(t('Job has been duplicated as a new job <a href="@url">#@id</a>.',
- array('@url' => url('admin/tmgmt/jobs/' . $resubmitted_job->tjid), '@id' => $resubmitted_job->tjid)));
- $this->drupalGet('admin/tmgmt/jobs');
- $elements = $this->xpath('//table[contains(@class, @view)]//td[contains(., @text)]',
- array('@view' => 'views-table', '@text' => t('N/A')));
- $status = $elements[0];
- $this->assertEqual(trim((string)$status), t('N/A'));
- }
- /**
- * Test the cart functionality.
- */
- function testCart() {
- $this->setEnvironment('fr');
- $job_items = array();
- // Create a few job items and add them to the cart.
- for ($i = 1; $i < 6; $i++) {
- $job_item = tmgmt_job_item_create('test_source', 'test', $i);
- $job_item->save();
- $job_items[$i] = $job_item;
- }
- $this->loginAsTranslator();
- foreach ($job_items as $job_item) {
- $this->drupalGet('tmgmt-add-to-cart/' . $job_item->tjiid);
- }
- // Check if the items are displayed in the cart.
- $this->drupalGet('admin/tmgmt/cart');
- foreach ($job_items as $job_item) {
- $this->assertText($job_item->label());
- }
- // Test the remove items from cart functionality.
- $this->drupalPost(NULL, array('items[1]' => TRUE, 'items[4]' => TRUE), t('Remove selected'));
- $this->assertText($job_items[2]->label());
- $this->assertText($job_items[3]->label());
- $this->assertText($job_items[5]->label());
- $this->assertNoText($job_items[1]->label());
- $this->assertNoText($job_items[4]->label());
- $this->assertText(t('Job items were removed from the cart.'));
- // Test that removed job items from cart were deleted as well.
- $existing_items = tmgmt_job_item_load_multiple(NULL);
- $this->assertTrue(!isset($existing_items[$job_items[1]->tjiid]));
- $this->assertTrue(!isset($existing_items[$job_items[4]->tjiid]));
- $this->drupalPost(NULL, array(), t('Empty cart'));
- $this->assertNoText($job_items[2]->label());
- $this->assertNoText($job_items[3]->label());
- $this->assertNoText($job_items[5]->label());
- $this->assertText(t('All job items were removed from the cart.'));
- // No remaining job items.
- $existing_items = tmgmt_job_item_load_multiple(NULL);
- $this->assertTrue(empty($existing_items));
- $language_sequence = array('en', 'en', 'fr', 'fr', 'de', 'de');
- for ($i = 1; $i < 7; $i++) {
- $job_item = tmgmt_job_item_create('test_source', 'test', $i);
- $job_item->save();
- $job_items[$i] = $job_item;
- $languages[$job_items[$i]->tjiid] = $language_sequence[$i - 1];
- }
- variable_set('tmgmt_test_source_languages', $languages);
- foreach ($job_items as $job_item) {
- $this->drupalGet('tmgmt-add-to-cart/' . $job_item->tjiid);
- }
- $this->drupalPost('admin/tmgmt/cart', array(
- 'items[' . $job_items[1]->tjiid . ']' => TRUE,
- 'items[' . $job_items[2]->tjiid . ']' => TRUE,
- 'items[' . $job_items[3]->tjiid . ']' => TRUE,
- 'items[' . $job_items[4]->tjiid . ']' => TRUE,
- 'items[' . $job_items[5]->tjiid . ']' => TRUE,
- 'target_language[]' => array('en', 'de'),
- ), t('Request translation'));
- $this->assertText(t('@count jobs need to be checked out.', array('@count' => 4)));
- // We should have four jobs with following language combinations:
- // [fr, fr] => [en]
- // [de] => [en]
- // [en, en] => [de]
- // [fr, fr] => [de]
- $jobs = entity_load('tmgmt_job', FALSE, array('source_language' => 'fr', 'target_language' => 'en'));
- $job = reset($jobs);
- $this->assertEqual(count($job->getItems()), 2);
- $jobs = entity_load('tmgmt_job', FALSE, array('source_language' => 'de', 'target_language' => 'en'));
- $job = reset($jobs);
- $this->assertEqual(count($job->getItems()), 1);
- $jobs = entity_load('tmgmt_job', FALSE, array('source_language' => 'en', 'target_language' => 'de'));
- $job = reset($jobs);
- $this->assertEqual(count($job->getItems()), 2);
- $jobs = entity_load('tmgmt_job', FALSE, array('source_language' => 'fr', 'target_language' => 'de'));
- $job = reset($jobs);
- $this->assertEqual(count($job->getItems()), 2);
- $this->drupalGet('admin/tmgmt/cart');
- // Both fr and one de items must be gone.
- $this->assertNoText($job_items[1]->label());
- $this->assertNoText($job_items[2]->label());
- $this->assertNoText($job_items[3]->label());
- $this->assertNoText($job_items[4]->label());
- $this->assertNoText($job_items[5]->label());
- // One de item is in the cart as it was not selected for checkout.
- $this->assertText($job_items[6]->label());
- }
- /**
- * Test if the source is able to pull content in requested language.
- */
- function testCartEnforceSourceLanguage() {
- $this->setEnvironment('sk');
- $this->setEnvironment('cs');
- module_enable(array('tmgmt_node'));
- $content_type = $this->drupalCreateContentType();
- $node_sk = $this->drupalCreateNode(array(
- 'title' => $this->randomName(),
- 'language' => 'sk',
- 'body' => array('sk' => array(array($this->randomName()))),
- 'type' => $content_type->type,
- ));
- $this->drupalCreateNode(array(
- 'title' => $this->randomName(),
- 'language' => 'en',
- 'tnid' => $node_sk->nid,
- 'body' => array('en' => array(array($this->randomName()))),
- 'type' => $content_type->type,
- ));
- $node_cs = $this->drupalCreateNode(array(
- 'title' => $this->randomName(),
- 'language' => 'cs',
- 'body' => array('cs' => array(array($this->randomName()))),
- 'type' => $content_type->type,
- ));
- $this->loginAsTranslator();
- $job_item_sk = tmgmt_job_item_create('node', 'node', $node_sk->nid);
- $job_item_sk->save();
- $this->drupalGet('tmgmt-add-to-cart/' . $job_item_sk->tjiid);
- $job_items_data[$job_item_sk->item_id] = $job_item_sk->item_type;
- $job_item_cs = tmgmt_job_item_create('node', 'node', $node_cs->nid);
- $job_item_cs->save();
- $this->drupalGet('tmgmt-add-to-cart/' . $job_item_cs->tjiid);
- $job_items_data[$job_item_cs->item_id] = $job_item_cs->item_type;
- $this->drupalPost('admin/tmgmt/cart', array(
- 'enforced_source_language' => TRUE,
- 'source_language' => 'en',
- 'items[' . $job_item_cs->tjiid .']' => TRUE,
- 'items[' . $job_item_sk->tjiid .']' => TRUE,
- 'target_language[]' => array('es')
- ), t('Request translation'));
- $this->assertText(t('One job needs to be checked out.'));
- $this->assertRaw(t('One item skipped. @language translation unavailable.', array('@language' => 'English')));
- $args = explode('/', $this->getUrl());
- $tjid = array_pop($args);
- $this->drupalPost(NULL, array(), t('Submit to translator'));
- // We cannot test for the item data as items without a job are not able to
- // get the data in case the source language is overridden. Therefore only
- // testing for item_id and item_type values.
- foreach (tmgmt_job_load($tjid)->getItems() as $job_item) {
- $this->assertEqual($job_items_data[$job_item->item_id], $job_item->item_type);
- }
- $this->drupalGet('admin/tmgmt/cart');
- $this->assertText($node_cs->title);
- $this->assertNoText($node_sk->title);
- }
- }
|