Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Field Value Tests #2459

Merged
merged 13 commits into from
May 17, 2019
Merged

Added Field Value Tests #2459

merged 13 commits into from
May 17, 2019

Conversation

BeksOmega
Copy link
Collaborator

The basics

  • I branched from develop
  • My pull request is against develop
  • My code follows the style guide

The details

Resolves

Work on #2169 specifically this.

Proposed Changes

Adds tests to document how fields should react to different values.

Also clarifies which fields have default values and which do not.

Reason for Changes

Tests needed to be added before #2441 possibly breaks the world.

Test Coverage

This is tests silly!

Additional Information

So... a lot of these tests are actually failing! But that's not because the tests are broken, it's because the fields are broken.

Examples:

  • Most validator tests fail because atm most fields don't validate unless they are attached to a block. (link)
  • All checkbox field tests are failing because checkboxes don't handle their text_ properly.
  • Lots of fields have less severe checks on setValue than on their constructors, so several setValue tests are failing (e.g. most fields fail the non-parsable string test).

Plus a smattering of other field-specific bugs.

Important information

I have some TODO notes in the tests, these are requests for information on what the intended behavior is.

For example, most fields will revert to a default value (meaning the value they would have had had they been constructed no parameters) when passed a non-parsable value. I believe this is a bug, and non-parsable values should validated to null inside a field's doClassValidation_ function, but I wanted confirmation.

Copy link
Contributor

@RoboErikG RoboErikG left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still need to work through the test cases, but wanted to ask why you're changing the behavior in labels and text inputs.

* @param {string=} opt_class Optional CSS class for the field's text.
* @extends {Blockly.Field}
* @constructor
*/
Blockly.FieldLabel = function(text, opt_class) {
if (!text) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be false for the empty string as well, which I don't think is a behavior change you intended to make.

In general, why are you adding this check? It looks like setText will handled it by casting to a String if it's not null. Is it a problem with validators?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, Fixed =)

@@ -46,6 +46,9 @@ goog.require('goog.userAgent');
* @constructor
*/
Blockly.FieldTextInput = function(text, opt_validator) {
if (!text) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue, and empty string should be a valid starting point for text input fields.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Copy link
Contributor

@RoboErikG RoboErikG left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed Angle and Checkbox.

  • This is really thorough. Impressive work. =)
  • In general, we should probably fix any fields that reset to the default when they get bad input. I'll file an issue.

test('Value -> Undefined', function() {
var angleField = new Blockly.FieldAngle(1);
angleField.setValue(undefined);
// TODO: Is this how we want this to work?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a bug in field angle. Should probably be treated the same as null.

test('Value -> Non-Parsable String', function() {
var angleField = new Blockly.FieldAngle(1);
angleField.setValue('bad');
// TODO: Is this how we want this to work?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto. Should probably not change the value if the input is invalid.

var actualValue = checkboxField.getValue();
var actualText = checkboxField.getText();
assertEquals(actualValue, expectedValue);
assertEquals(actualText, expectedText);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for checking both the value and the text. That'll make supporting screen readers easier.

assertValue(checkboxField, 'TRUE', 'true');
});
});
suite('Always True Validator', function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/True/False

@RoboErikG
Copy link
Contributor

Filed #2462 for to track bad value handling in fields.

Copy link
Contributor

@RoboErikG RoboErikG left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

colour and date reviewed.

var colourField = new Blockly.FieldColour('bad');
assertValueDefault(colourField);
});
// TODO: Do we want the colour to convert to uppercase or to lowercase?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lowercase sgtm.

});
suite('setValue', function() {
test('#ffffff -> Null', function() {
var colourField = new Blockly.FieldColour('#ffffff');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating the colourField can be moved into setup for this suite. Also, you should use a value other than the default so you can distinguish when it's falling back to the default vs the current value.

suite('Validators', function() {
var colourField;
setup(function() {
colourField = new Blockly.FieldColour('#ffffff');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a colour string other than the default so you can distinguish default vs previous value.

assertEquals(actualText, expectedText);
}
function assertValueDefault(colourField) {
var expectedValue = Blockly.FieldColour.COLOURS[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For testing consistency you might want to change Blockly.FieldColour.COLOURS to a specific set of colours in setup and change it back in teardown.

var actualValue = dateField.getValue();
var actualText = dateField.getText();
var stringExpectedValue = String(expectedValue);
assertEquals(String(actualValue), stringExpectedValue);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you compare the values as strings?

assertValue(dateField, '2020-02-20');
});
test('Invalid Date - Month(2020-13-20)', function() {
// TODO: What do we want it to do in this case?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test as written seems reasonable to me.

assertValueDefault(dateField);
});
test('Invalid Date - Day(2020-02-32)', function() {
// TODO: What do we want it to do in this case?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto.

test('Value -> Undefined', function() {
var dateField = new Blockly.FieldDate('2020-02-20');
dateField.setValue(undefined);
// TODO: Is this how we want this to work?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, probably want it to leave the current value instead of reverting to the default.

@rachel-fenichel
Copy link
Collaborator

For tests that fail now because the fields do the wrong thing--you can write them as test.skip('Name', function() {}); or suite.skip('Name', function() {});

The tests will show up on the index page, but won't be run. Then you can enable them as you fix the bugs.

Copy link
Contributor

@RoboErikG RoboErikG left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed remaining test files.

@@ -48,11 +48,20 @@ Blockly.FieldImage = function(src, width, height,
opt_alt, opt_onClick, opt_flipRtl) {
this.sourceBlock_ = null;

if (!src) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a change in behavior. I think there's a valid use case for initializing src to null or an empty string and setting it later, so you should probably revert this and let the image element handle the error.

function assertValue(dropdownField, expectedValue, expectedText) {
var actualValue = dropdownField.getValue();
var actualText = dropdownField.getText();
assertEquals(String(actualValue), expectedValue);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the cast to string?

});
test.skip('Without Alt', function() {
var imageField = new Blockly.FieldImage('src', 1, 1);
// TODO: currently getText returns undefined, is that what we want?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It probably makes more sense to have it default to an empty string like you're testing for. =)

});
suite('setValue', function() {
test('Null', function() {
var labelField = new Blockly.FieldLabelSerializable('value');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pull out into setup for the setValue suite.

});
suite('setValue', function() {
test('Null', function() {
var labelField = new Blockly.FieldLabel('value');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pull out into setup method

});
test('When Not Editing', function() {
numberFieldField.setValue(2);
assertValue(numberFieldField, 2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't a null validator cause the value to be reverted?

});
suite('setValue', function() {
test('Null', function() {
var textInputField = new Blockly.FieldTextInput('value');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pull out into setup?

@@ -8,54 +8,46 @@ suite('Variable Fields', function() {
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we forgot to add a license header. Would you mind adding one?

assertNotNull(actualText);
assertNotUndefined(actualText);
}
function assertValueName(variableField, expectedText) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally you'd want to assert the id and the text are as expected. You may need to replace the method that generates ids for variables with a mock to do this, though. Specificially getOrCreateVariablePackage.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up stubbing Blockly.utils.genUid and Blockly.Variables.generateUniqueName instead. It should work the same.

{ variable:variableName }), workspace);
}
function assertValueDefault(variableField) {
var actualValue = variableField.getValue();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From code, the default name will be 'i' and the default value will be a random UUID. It might be worth mocking some of the methods in Blockly.Variables so you can control the defaults and make the tests for default clearer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: It looks like sinon is the proper way to do mocking in mocha.

Example: https://github.com/google/blockly/blob/develop/tests/mocha/event_test.js#L84
Docs: https://sinonjs.org/

@BeksOmega
Copy link
Collaborator Author

@RoboErikG should be good to go.

Copy link
Contributor

@RoboErikG RoboErikG left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good to me. A few final comments and I think Rachel wanted to glance over it too.

This was a lot of work. Thanks for doing it so thoroughly and thoughtfully, Beks!

* @param {string=} opt_alt Optional alt text for when block is collapsed.
* @param {Function=} opt_onClick Optional function to be called when the image
* is clicked. If opt_onClick is defined, opt_alt must also be defined.
* @param {boolean=} opt_flipRtl Whether to flip the icon in RTL.
* @extends {Blockly.Field}
* @constructor
*/
Blockly.FieldImage = function(src, width, height,
Blockly.FieldImage = function(opt_src, width, height,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We typically only label something opt if you can leave it out of the list of parameters. Since it's before width and height you have to supply something, but it's allowed to be null.

assertValue(labelField, '1');
});
test('Boolean', function() {
var labelField = new Blockly.FieldLabel(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized, you're testing for true but not for false. Will false fail since you've updated the code to replace falsey things with an empty string?

* @param {string=} opt_class Optional CSS class for the field's text.
* @extends {Blockly.Field}
* @constructor
*/
Blockly.FieldLabel = function(text, opt_class) {
this.size_ = new goog.math.Size(0, 17.5);
this.class_ = opt_class;
this.setValue(text);
this.setValue(text || '');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't think enough about this before. In your tests you require 1 -> '1' and true -> 'true', but not 0 -> '0' and false -> 'false' . It looks like those values will result in the empty string instead.

Here and elsewhere can we update this to explicitly check for null/undefined instead and let the majority of things be auto-cast to strings?

Copy link
Collaborator Author

@BeksOmega BeksOmega May 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed for all applicable fields & added tests to cover the changes.

@rachel-fenichel
Copy link
Collaborator

I'm taking a look now, will comment when I'm done. @RoboErikG thanks for the thorough review, and thanks @BeksOmega for the thorough tests!

Copy link
Collaborator

@rachel-fenichel rachel-fenichel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have one comment on a specific test, and one comment on structure.

assertValue(angleField, 1);
});
test('Value -> >360°', function() {
var angleField = new Blockly.FieldAngle(1);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set initial value to 2 to make sure it changed.

});
});
suite.skip('Validators', function() {
var angleField;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is angleField defined here instead of in setup?

What I expected to see:

setup(function() {
  this.angleField = new Blockly.FieldAngle(1);
  ...
});

test('Test name', function() {
  this.angleField.isBeingEdited_ = true;
  ...
});

@rachel-fenichel
Copy link
Collaborator

I'm happy with this! @RoboErikG please confirm and then merge.

Copy link
Contributor

@RoboErikG RoboErikG left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed lgtm.

@RoboErikG RoboErikG merged commit acd96aa into google:develop May 17, 2019
@BeksOmega BeksOmega deleted the tests/setValue branch May 19, 2019 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants