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

Add JavaScript unit tests and refactor #35

Merged
merged 65 commits into from
Feb 16, 2017

Conversation

sirbrillig
Copy link
Contributor

@sirbrillig sirbrillig commented Jan 31, 2017

Add basic JavaScript unit tests for wp.widgets.Form. They can be run using npm test on the command-line (you may need to run npm install first to get the tests properly set up).

This also makes some changes to the wp.widgets.Form class to make the code more concise and more testable. There are also a few bugs that are fixed. Changes include:

  • Explicitly pass underscore to the IIFE
  • Remove unnecessary use of _.clone
  • Export Form as a module when within CommonJS
  • Update Form.initialize to deep merge config with defaults
  • Remove merging default_instance with the model in all places except Form.getValue
  • Remove adding missing title property to model in Form.sanitize
  • Simplify the Form.model.validate method
  • Simplify the Form.initialize constructor
  • Add Form.renderNotificationsToContainer to allow testing Form.renderNotifications
  • Always remove all sanitize-added notifications in Form.model.validate

This needs to be tested with a real-world Form to be sure that the above changes do not break anything.

There's also more simplifications that could be made, but I wanted to get a PR up for the purposes of discussion.

Changes the function to accept an object, alter that object's prototype,
and return it. The caller is then responsible for passing the
constructor function to change.

This allows the function to be more easily tested since it does not
directly mutate global variables (although of course changing the
prototype of a global variable will have a a global effect).

Also adds `wp` and `_` as parameters to the IIFE to make their usage
explicit.
Not currently using real Controls, but it's something.
IIFEs are good tools for encapsulating functionality and to maintain
that encapsulation, all inputs and outputs (the exported API) need to be
explicit. To simply alias variables, variable assignment is a less
cumbersome tool.
`_.clone()` only performs a shallow copy and `_.extend()` is already
doing that here.
Debounced functions are async by nature and harder to test. This
rewrites `renderNotifications` as a debounced proxy for
`renderNotificationsToContainer`.
@sirbrillig sirbrillig changed the title Add unit tests Add JavaScript unit tests Jan 31, 2017
@sirbrillig
Copy link
Contributor Author

As part of the changes I simplified two-way data binding a bit (see 16f9762). I'm not sure if the resulting simplified version covers everything that the sync version did. The primary difference is that we're only listening to the change event, rather than propertychange, keyup, and input. There's lots of other ways to sync the data so if this doesn't work we can try something else.

@westonruter
Copy link
Contributor

@sirbrillig oh, I didn't consider how createSyncedPropertyValue related here. A problem with change is that it will only trigger when the user has blurred the field, resulting in a poorer preview experience. This is what wp.customize.Element is supposed to abstract away, using the appropriate DOM events to listen for changes to the underlying input.

I think that createSyncedPropertyValue needs to be restored, but with a change to how sync is done. Inside of propertyChangeListener we can do this change instead:

--- a/js/widget-form.js
+++ b/js/widget-form.js
@@ -272,15 +272,15 @@ wp.widgets.Form = (function( api, $ ) {
 		 * @returns {object} Property value instance.
 		 */
 		createSyncedPropertyValue: function createSyncedPropertyValue( root, property ) {
-			var propertyValue, rootChangeListener, propertyChangeListener;
+			var form = this, propertyValue, rootChangeListener, propertyChangeListener;
 
 			propertyValue = new api.Value( root.get()[ property ] );
 
 			// Sync changes to the property back to the root value.
 			propertyChangeListener = function( newPropertyValue ) {
-				var rootValue = _.clone( root.get() );
-				rootValue[ property ] = newPropertyValue;
-				root.set( rootValue );
+				var newState = {};
+				newState[ property ] = newPropertyValue;
+				form.setState( newState );
 			};
 			propertyValue.bind( propertyChangeListener );

This will ensure that the model is not directly mutated so that our validate logic will still apply.

Does that make sense?

This makes sure that data from the DOM is validated before changing the
model.
@sirbrillig
Copy link
Contributor Author

Ah, nice. Don't know why I didn't see that part of the function before. I guess I wasn't clear what was going on in there. I made the changes you suggested and the tests all pass. 👍 (I rebased away the old version.)

One last question:

In renderNotificationsToContainer, this command is run to change the CSS of the notifications container after it is shown: container.css( 'height', 'auto' );. I'm curious why that's only done when the container is shown and it's not changed when it's hidden? Could the height just be part of the actual CSS for the Form instead of being set with javascript? I guess that also makes me wonder: why not do the animation with CSS instead of jQuery?

@westonruter
Copy link
Contributor

Ah, nice. Don't know why I didn't see that part of the function before. I guess I wasn't clear what was going on in there. I made the changes you suggested and the tests all pass.

Great!

I'm curious why that's only done when the container is shown and it's not changed when it's hidden? Could the height just be part of the actual CSS for the Form instead of being set with javascript? I guess that also makes me wonder: why not do the animation with CSS instead of jQuery?

@sirbrillig Humm, I don't have a good answer for you. I can't remember why height:auto was added 😦 If you see no good reason for it, the I'd say to go ahead and remove. The styling/animation logic here is copied from wp.customize.Control#renderNotifications. If you have a CSS solution then more power to you! But I think that CSS transitions/animations weren't used because they are not supported by IE<11.

@sirbrillig
Copy link
Contributor Author

TIL that CSS transitions aren't available in IE < 10. It's amazing how quickly I've come to think of them as natural. Well, I don't know why height: auto is in there either, but it's a relatively minor thing either way. I'd be inclined to just remove it and re-add it if/when it becomes apparent that there's a problem that needs to be solved.

@westonruter
Copy link
Contributor

Great! I'll give this PR a test soon and see if there's any issues on my end and then merge. Excellent work here. Really appreciate the depth you went and the insights you have.

@westonruter
Copy link
Contributor

@sirbrillig I found that the widgets weren't getting id_base defined on the widget prototypes, so I added 5d7ccb0. This means a widget should be defined via:

wp.widgets.formConstructor.foo = wp.widgets.Form.extend({
     id_base: 'foo'
});

So there is some redundancy there. In reality they should never be different, so it seems a shame to duplicate.

@sirbrillig
Copy link
Contributor Author

So there is some redundancy there. In reality they should never be different, so it seems a shame to duplicate.

I'm not sure I understand. Do you mean, the id_base property should always be the same as the name of the property or variable to which that object is assigned?

So const HelloWidget = Form.extend( {} ) should result in HelloWidget.id_base === 'HelloWidget'?

@westonruter
Copy link
Contributor

westonruter commented Feb 14, 2017

I'm not sure I understand. Do you mean, the id_base property should always be the same as the name of the property or variable to which that object is assigned?

@sirbrillig
Copy link
Contributor Author

I removed id_base from Form. Let me know if there's anything else!

@westonruter westonruter changed the title Add JavaScript unit tests Add JavaScript unit tests and refactor Feb 16, 2017
@westonruter westonruter merged commit 9c764ad into xwp:develop Feb 16, 2017
@westonruter westonruter modified the milestone: 0.4.0 Feb 17, 2017
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.

2 participants