Comments

jamesdixon created an issue. See original summary.

jamesdixon’s picture

Status: Active » Needs work
StatusFileSize
new3.02 KB

Here's some really rough progress, saving for use at another computer.

jamesdixon’s picture

StatusFileSize
new5.79 KB

Further progress...

jamesdixon’s picture

StatusFileSize
new6.43 KB

Hopefully patch file uploads this time...

jamesdixon’s picture

StatusFileSize
new7.27 KB

More progress on this one, nearly done the plugin itself.

jamesdixon’s picture

+++ b/src/Plugin/Tamper/KeywordFilter.php
@@ -0,0 +1,198 @@
+    $config[self::SETTING_FUNCTION] = '_feeds_tamper_keyword_filter_match_regex';

Note to self, this isn't going to work since it's not a procedural function anymore.

jamesdixon’s picture

StatusFileSize
new10.37 KB

Made more progress here.

jamesdixon’s picture

+++ b/src/Plugin/Tamper/KeywordFilter.php
@@ -0,0 +1,204 @@
+  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {

The difficulty I'm having with writing tests for this plugin, is in the original version, it generated a ton of settings values through the following function which occurs at the time of form validation. Is there a way to make a mock $form and $form_state object so I can run validateConfigurationForm() inside the tests? Appreciate your guidance.

jamesdixon’s picture

Title: Keword Filter Plugin » Keyword Filter Plugin
megachriz’s picture

You could mock FormStateInterface and in order to return a value for $form_state->getValue() you could let PHPUnit return a value map: https://phpunit.de/manual/6.5/en/test-doubles.html#test-doubles.stubs.ex...

Example (untested):

$map = [
  [KeywordFilter::SETTING_WORDS, 'booya'],
  [KeywordFilter::SETTING_WORD_BOUNDARIES, FALSE],
];
$form_state = $this->createMock(FormStateInterface::class);
$form_state->expects($this->any())
  ->method('getValue')
  ->will($this->returnValueMap($map));

An other option is to use Prophecy: https://github.com/phpspec/prophecy
I haven't used it much yet, but I believe it would work something like this:

$form_state = $this->prophesize(FormStateInterface::class);
$form_state->getValue(KeywordFilter::SETTING_WORDS)->willReturn('booya');
$form_state->getValue(KeywordFilter::SETTING_WORD_BOUNDARIES)->willReturn(FALSE);

As you see, it replaces ->method('methodName') with ->methodName() as if you were actually calling the method. It could be that the order in which the method is called with each given value matters, I'm not sure.

Finally, if a method tends to become very complex, it could be a good idea to put part of it into a new protected method. This way, you could write an unit test for just that piece of code in validateConfigurationForm() that doesn't necessarily do validating. Not sure if it would be a good idea to that in this case as I see in the more complex part of the method also a call to setErrorByName() on the form state object.

jamesdixon’s picture

Awesome, thanks for the advice @MegaChriz! Very helpful, I'll give this a shot.

jamesdixon’s picture

StatusFileSize
new10.77 KB
new4.14 KB

I'm getting closer here is my latest progress. Having some trouble with validateConfigurationForm() I'll explain soon.

jamesdixon’s picture

+++ b/tests/src/Unit/Plugin/Tamper/KeywordFilterTest.php
@@ -0,0 +1,105 @@
+  public function testStriPosFilter() {
+    $form = [];
+    $map = [
+      [KeywordFilter::SETTING_WORDS, 'booya'],
+      [KeywordFilter::SETTING_WORD_BOUNDARIES, FALSE],
+      [KeywordFilter::SETTING_EXACT, FALSE],
+      [KeywordFilter::SETTING_CASE_SENSITIVE, FALSE],
+      [KeywordFilter::SETTING_INVERT, FALSE],
+    ];
+    $form_state = $this->createMock(FormStateInterface::class);
+    $form_state->expects($this->any())
+      ->method('getValue')
+      ->will($this->returnValueMap($map));
+
+    $this->plugin = new KeywordFilter([], 'keyword_filter', []);
+    $this->plugin->submitConfigurationForm($form, $form_state);
+    $this->assertEquals('', $this->plugin->tamper('This is a title'));
+  }

The trouble is $this->plugin->submitConfigurationForm() is not claling $this->plugin->validateConfigurationForm() at all. I have thrown var_dumps() inside the validate function and it's being ignored. Not sure, I tried validateForm() also but no luck. Is validateConfigurationForm() the correct function for form validation in D8?

megachriz’s picture

Submitting forms don't necessarily work in unit tests. If you call methods like validateConfigurationForm() in unit tests, you want to purely test the code in validateConfigurationForm(). The test method would then be called testValidateConfigurationForm(), testConfigurationForm() or alike. Feeds has some example unit tests for configuration forms. See the test classes in feeds/tests/src/Unit/Feeds/Fetcher/Form.

It looks like that the test testStriPosFilter() shouldn't be focussing on the form functions at all. Testing the Tamper plugins behavior when calling tamper() on it should focus on providing the Tamper configuration as it gets stored, and disregard the fact that these values get there via a form.

Tests that test validating or submitting the form, should test what configuration gets stored based on the input values. Testing what tamper() would then do is out of scope for that test.

ericgsmith’s picture

I haven't take a thorough look but be caution of the way you are using setConfiguration.

This is from the ConfigurablePluginInterface - it is expected to be the whole configuration of the plugin, e.g. when the plugin is created. Its purpose is not for individual values, it is expected the whole configuration.

By setting an individual value, you are saying set this value and use the default values for everything else.

E.g instead of

$this->setConfiguration([self::SETTING_WORDS => $form_state->getValue(self::SETTING_WORDS)]);
$this->setConfiguration([self::SETTING_WORD_BOUNDARIES => $form_state->getValue(self::SETTING_WORD_BOUNDARIES)]);
$this->setConfiguration([self::SETTING_EXACT => $form_state->getValue(self::SETTING_EXACT)]);
$this->setConfiguration([self::SETTING_CASE_SENSITIVE => $form_state->getValue(self::SETTING_CASE_SENSITIVE)]);
$this->setConfiguration([self::SETTING_INVERT => $form_state->getValue(self::SETTING_INVERT)]);

It needs to be

$this->setConfiguration([
  self::SETTING_WORDS => $form_state->getValue(self::SETTING_WORDS),
  self::SETTING_WORD_BOUNDARIES => $form_state->getValue(self::SETTING_WORD_BOUNDARIES),
  self::SETTING_EXACT => $form_state->getValue(self::SETTING_EXACT),
  self::SETTING_CASE_SENSITIVE => $form_state->getValue(self::SETTING_CASE_SENSITIVE),
  self::SETTING_INVERT => $form_state->getValue(self::SETTING_INVERT),
]);
jamesdixon’s picture

Thanks @MegaChriz and @ericgsmith for the guidance. With your advice I hope to wrap this one up soon.

jamesdixon’s picture

Status: Needs work » Needs review
StatusFileSize
new17.13 KB

Oh man this one was a beast of a plugin to test!

Appreciate all your advice on this one guys. I am bracing for impact with the review. This plugin seems to have been built in a very different style than the others in D7.

Status: Needs review » Needs work

The last submitted patch, 17: keywordfilter-2976176-17.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

jamesdixon’s picture

Ah I need schema for hidden config options here, that will be quick

jamesdixon’s picture

StatusFileSize
new17.32 KB

Lets try that again. Not sure if I got the "array" type for word_list correct in the yml as there were no examples I could find in the file.

jamesdixon’s picture

Status: Needs work » Needs review

The last submitted patch, 2: keywordfilter-2976176-2.patch, failed testing. View results

The last submitted patch, 4: keywordfilter-2976176-4.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

The last submitted patch, 5: keywordfilter-2976176-5.patch, failed testing. View results

The last submitted patch, 7: keywordfilter-2976176-7.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

The last submitted patch, 12: keywordfilter-2976176-12.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Status: Needs review » Needs work

The last submitted patch, 20: keywordfilter-2976176-20.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

jamesdixon’s picture

Status: Needs work » Needs review
StatusFileSize
new17.38 KB

I think what I was looking for was a sequence type:

https://www.drupal.org/files/ConfigSchemaCheatSheet1.5.pdf

Lets give this a shot.

ericgsmith’s picture

Status: Needs review » Needs work

Thanks James - massive effort, looks like a fairly complex set of options to manage. I'm going to have a bit of a think about how the tamper method is being called, for now here is a few bits of minor feedback.

  1. +++ b/src/Plugin/Tamper/KeywordFilter.php
    @@ -0,0 +1,210 @@
    + * @Tamper(
    

    Needs the handles multiple annotation property

  2. +++ b/src/Plugin/Tamper/KeywordFilter.php
    @@ -0,0 +1,210 @@
    +      [self::SETTING_WORDS => $form_state->getValue(self::SETTING_WORDS)],
    

    This can only work with an array of configuraiton, here it is passing in 5 arrays.

  1. +++ b/src/Plugin/Tamper/KeywordFilter.php
    @@ -0,0 +1,210 @@
    +    global $multibyte;
    

    This has been removed in Drupal 8. See https://www.drupal.org/node/1992584 for the change record. We will need to work with the new Unicode class.

  2. +++ b/src/Plugin/Tamper/KeywordFilter.php
    @@ -0,0 +1,210 @@
    +            $form_state->setErrorByName(conf::SETTING_WORDS, $this->t('Search text must begin and end with a letter, number, or underscore to use the %option option.', ['%option' => t('Respect word boundaries')]));
    

    Typo - conf looks like it should be self

  3. +++ b/src/Plugin/Tamper/KeywordFilter.php
    @@ -0,0 +1,210 @@
    +    $this->setConfiguration($config);
    

    I'm not a fan of setting any configuration in the validation method.

    Validation should be ensuring the all the form values are correct. If we need to massage or change any of the user input, I think its more common to put those values back into the form state object.

    We should only be configuring the plugin in the submit handler.

  4. +++ b/src/Plugin/Tamper/KeywordFilter.php
    @@ -0,0 +1,210 @@
    +  protected function feedsTamperKeywordFilterMatch($match_func, $field, array $word_list) {
    

    Naming conventions for methods should reflect what this is doing, e.g. "match" - we don't need to prefix it with our module / plugin names.

vijay.mayilsamy’s picture

StatusFileSize
new17.17 KB
new7.95 KB

Thanks for the feedback, Eric

@James, I have managed fix the above feedbacks except this one.

+++ b/src/Plugin/Tamper/KeywordFilter.php
@@ -0,0 +1,210 @@
+ $this->setConfiguration($config);
I'm not a fan of setting any configuration in the validation method.

Validation should be ensuring the all the form values are correct. If we need to massage or change any of the user input, I think its more common to put those values back into the form state object.

We should only be configuring the plugin in the submit handler.

mitrpaka’s picture

Status: Needs work » Needs review
StatusFileSize
new17.29 KB
new3.13 KB

Updated patch with changes in validation and submit handler to handle configuration settings in submit handler only.

volkswagenchick’s picture

Issue tags: +dcasheville19

Tagging for DrupalCamp Asheville

volkswagenchick’s picture

Issue tags: +dcco2019

Tagging for the next to North American contrib days, Asheville and Colorado

DrupalCamp Asheville contrib days are July 13-14, 2019
DrupalCamp Colorado contrib day is Aug 4, 2019

andypost’s picture

I'd like to set RTBC but constants needs better names and docs

+++ b/src/Plugin/Tamper/KeywordFilter.php
@@ -0,0 +1,211 @@
+  const SETTING_WORDS = 'words';
+  const SETTING_WORD_BOUNDARIES = 'word_boundaries';
+  const SETTING_EXACT = 'exact';
+  const SETTING_CASE_SENSITIVE = 'case_sensitive';
+  const SETTING_INVERT = 'invert';
+  const SETTING_WORD_LIST = 'word_list';
+  const SETTING_REGEX = 'regex';
+  const SETTING_FUNCTION = 'function';
...
+    $config[self::SETTING_WORDS] = '';
+    $config[self::SETTING_WORD_BOUNDARIES] = FALSE;
+    $config[self::SETTING_EXACT] = FALSE;
+    $config[self::SETTING_CASE_SENSITIVE] = FALSE;
+    $config[self::SETTING_INVERT] = FALSE;
+    $config[self::SETTING_WORD_LIST] = [];
+    $config[self::SETTING_REGEX] = FALSE;
+    $config[self::SETTING_FUNCTION] = 'matchRegex';

It really hard to read "SETTINGS_" prefix and harder to type (use in code)
Also each one needs docs

MoCart’s picture

I tried patches #22 and #30 in DP 8.7.7 (feeds 8.x-3.0-alpha5, feeds_tamper 8.x-2.0-beta1, feeds_ex 8.x-1.0-alpha2), and something still seems to be missing.

I get error "method not found" for "feedsTamperKeywordFilterMatch". Is there another patch file for KeywordFilter.php with that function?

Also, I was getting a form validation error until I added “use Drupal\Component\Utility\Unicode;” to the header of KeywordFilter.php.

MoCart’s picture

OK, patch #31 works. You can disregard my #35 post.

mmaranao’s picture

Hi guys, this looks like the filter I'm looking for but is there a way to return the key for the matched keyword instead of the values?

array('Item1' => 'Yes', 'Item2' => 'No', 'Item3' => 'No');
// Keyword filter 'Yes', should return array('Item1');

Thanks for your help!

jamesdixon’s picture

Lets update the test to work with the new setup (ie: add that extra parameter when creating a new test object).

Then we can see if it passes.

jamesdixon’s picture

Status: Needs review » Needs work
zabej’s picture

Status: Needs work » Needs review
StatusFileSize
new17.74 KB
new6.22 KB

@jamesdixon made several changes here. Please have a look.

jamesdixon’s picture

StatusFileSize
new6.21 KB

Thanks @zabej.

I see @andypost had some suggestions on improving the code base in #34

I'd say we need to:

1) Remove the SETTING_ part of all the PHP constants and make sure we don't miss any so the code functions the same.
2) I'm finding conflicting information on coding standards for documenting consts. I think this would be straight forward:

So above each const lets document what it is, using clues from the description of each form field.

For example:

/** A list of words/phrases that need to appear in the text. */
const WORDS = 'words';
andypost’s picture

jamesdixon’s picture

StatusFileSize
new7.49 KB

Thanks for directing us the the right docs @andypost.

For 2) We'd want to use this style:

zabej’s picture

Hello @james,
Thanks for clarifications.
I've create one more changes package. Just a remark.

The three constants below has no any visual filter (in attachment https://www.drupal.org/files/issues/2020-05-28/admin-structure-feeds-man...). I also did not find them on tamper page. Please clarify where can I get to know the description. I add for regex something but not sure

  /**
   * WORD_LIST NTB.
   */

  const WORD_LIST = 'word_list';

  /**
   * If checked, use Regex mode for processing.
   */

  const REGEX = 'regex';

  /**
   * Particular regular expression for processing data.
   */

  const FUNCTION = 'function';
jamesdixon’s picture

After removing math plugin updates here's what I recommend:

1) Looks like WORD_LIST is a computed value of SETTING_WORDS based on settings that were added.

We could do:

/**
 * Holds the calculated value of the word list after all settings are applied.
 */

const WORD_LIST = "word_list";

2) That REGEX settings is also calculated and not an exposed option.

/**
 * Flags whether or not we'll be using regex to match. This value is calculated by other options.
 */

const REGEX = "regex";

3) The FUNCTION setting controls which function we're using (mb_stripos or mb_strpos)

/**
 * Holds which string position function will be used.  Calculated by other options.
 */

const FUNCTION = "function";

These are all configuration option array indexes though, not sure if there's a better way to document them than describing what the config option is for.

jamesdixon’s picture

Okay I think it makes sense to add the following to the front of each of these:

Index for the word list configuration option.

Where we'd replace word list with whatever the option is. Then we add the description after. So a full example would be:

/**
 * Index for the word list configuration option.  The word list option holds the calculated 
 * value of the word list after all settings are applied.
 */

const WORD_LIST = "word_list";
andypost’s picture

StatusFileSize
new16.51 KB
new15.36 KB

Re-roll (math accepted), fix CS around consts and address last 2 comments

Also converted test to use data provider so all sets for test in one place

PS: sadly interdiff bugger then patch

zabej’s picture

Hello @andypost

What do you mean under "PS: sadly interdiff bugger then patch" ?

andypost’s picture

@zabej It means that reading the patch more productive then interdiff which is repeatable pattern

  • jamesdixon committed e7c49aa on 8.x-1.x
    Issue #2976176 by jamesdixon, zabej, andypost, vijay.mayilsamy, mitrpaka...
jamesdixon’s picture

Status: Needs review » Fixed

@andypost: thanks for recommending the docs cleanup and fixing that up.

Thanks everyone!

Looks good to me. Added to 8.x-1.x in commit e7c49aab7b718f1e5509546f6ec1a7df52fa20d4.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.