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

[2.x] Add legacy-data-mixin as 1.x->2.x migration aide. Fixes #5262. #5266

Merged
merged 22 commits into from
Nov 2, 2018

Conversation

kevinpschaaf
Copy link
Member

@kevinpschaaf kevinpschaaf commented Jun 21, 2018

Add legacy-data-mixin as 1.x->2.x migration aide.

Mixin to selectively add back Polymer 1.x's undefined rules governing when observers & computing functions run based on all arguments being defined (reference https://www.polymer-project.org/1.0/docs/devguide/observers#multi-property-observers).

When polymer/lib/legacy/legacy-data-mixin.html is loaded at the app level, all legacy elements (defined with Polymer({...})) will have the mixin applied. The mixin only restores legacy data handling if _legacyUndefinedCheck: true is set on the element's prototype.

This mixin is intended for use to help migration from Polymer 1.x to 2.x+ by allowing legacy code to work during the migration period, while identifying observers and computing functions that need undefined checks to work without the mixin in Polymer 2. It is generally not intended for use in production.

Reference Issue

Fixes #5262.
Supersedes and closes #5263

@kevinpschaaf kevinpschaaf changed the base branch from master to 2.x June 21, 2018 00:50
@Polymer Polymer deleted a comment from googlebot Jun 21, 2018
@kevinpschaaf
Copy link
Member Author

This is an alternate implementation of #5263, based on same concept there, with the goal of making the code to implement the legacy behavior purely opt-in. cc: @beckysiegel

@kevinpschaaf
Copy link
Member Author

Two affordances were made in core to enable writing such a mixin:

  1. Allow marshalArgs to be overridable via mixin my moving it from a private function to a protected function
  2. Extend the API for Polymer.Class to allow a mixin to be applied prior to desugaring the info object into subclasses of the base LegacyElementMixin.

These seem like generally good changes on their own.

Copy link
Contributor

@TimvdLippe TimvdLippe left a comment

Choose a reason for hiding this comment

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

Some small nits. Other than that LGTM!

if (this._legacyUndefinedCheck && args.length > 1) {
for (let i=0; i<args.length; i++) {
if (vals[i] === undefined) {
throw new UndefinedArgumentError(`argument '${args[i].name}' is undefined; ensure it has an undefined check`);
Copy link
Contributor

Choose a reason for hiding this comment

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

If I understand correctly, we are throwing an error here to break out of the control flow, as code later executed would erroneously run? If that is correct, maybe add a comment to make this explicit?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

});

const Class = Polymer.Class;
Polymer.Class = info => Class(info, Polymer.LegacyDataMixin);
Copy link
Contributor

Choose a reason for hiding this comment

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

This would break if a mixin was passed in to this function. Therefore, I think the mixin should be optional and the mixin should be conditionally applied to this mixin.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah good catch. The mixin option was a late addition.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, good catch. Updated.

assert.equal(el.static.callCount, 1);
assert.equal(el.one.callCount, 1);
assert.equal(el.two.callCount, 0);
assert.isTrue(console.warn.calledOnce);
Copy link
Contributor

Choose a reason for hiding this comment

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

To make this more uniform, make this the same as line 239. E.g. el.two.callCount, 1

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup, thanks.

@TimvdLippe
Copy link
Contributor

Also the tests appear to fail on IE11, likely related to how it handles exceptions. And the latest version of shadydom appears to be now failing some of our tests. We have to look into these regressions separately.

@beckysiegel
Copy link
Contributor

Thanks!

(function() {
'use strict';

const UndefinedArgumentError = class extends Error {};
Copy link
Contributor

Choose a reason for hiding this comment

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

If we want this error to have a meanifulful name this pattern should be followed https://azimi.me/2015/09/23/high-custom-error-class-es6.html

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

* @return {Array<*>} Array of argument values
* @private
*/
_marshalArgs(args, path, props) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this function has a non-trivial cyclical complexity score, can you please add unit tests to ensure the functionality is correct. Also it might be a sign that the logic should be split up into smaller more meaningful functions to maintain a higher degree of readability.

*
* @param {!Array<!MethodArg>} args Array of argument metadata
* @param {string} path Property/path name that triggered the method effect
* @param {Object} props Bag of current property changes
Copy link
Contributor

Choose a reason for hiding this comment

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

what is a bag? perhaps restate as a "hash" or key value collection of ...

</head>
<body>

<script>
Copy link
Contributor

Choose a reason for hiding this comment

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

can you drop this since it is empty?

document.body.removeChild(el);
});
});
// ------------------------------------------------
Copy link
Contributor

Choose a reason for hiding this comment

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

is this comment needed?

Copy link
Member Author

Choose a reason for hiding this comment

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

Did some refactoring

Copy link
Contributor

Choose a reason for hiding this comment

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

thanks, much nicer!

*
* When loaded, all legacy elements (defined with `Polymer({...})`)
* will have the mixin applied. The mixin only restores legacy data handling
* if `_legacyUndefinedCheck: true` is set on the element's prototype.
Copy link
Contributor

Choose a reason for hiding this comment

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

any reason why we are _ prefixing this prototype value?

Copy link
Member Author

Choose a reason for hiding this comment

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

As a convention, we generally underscore any property not intended as part of the DOM element's public API, and consider them "protected". https://www.polymer-project.org/2.0/docs/devguide/properties#private-and-protected-properties

el.a = undefined;
el.b = undefined;
assert.equal(el.static.callCount, 1);
assert.equal(el.one.callCount, 2);
Copy link
Contributor

Choose a reason for hiding this comment

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

instead of callCount spy's can we sideeffect in the calls by incrementing a counter and then the tests are totally isolated without having to spy or mock on anything.

For example

foo(a, b) {
   this.fooCall++;
}

</test-fixture>

<script>
suite('imperative', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This suite of tests has a lot of duplication I think we could do with some test helpers to significantly clean up the test implementation to make the code easier to maintain and easier to reason about.

if (!window.customElements) {
window.customElements = {};
if (window.customElements) {
customElements.forcePolyfill = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

this change seems unrelated to the intent of the CL?

Copy link
Member Author

Choose a reason for hiding this comment

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

Separated out to #5268

Copy link
Contributor

Choose a reason for hiding this comment

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

thank you

@kevinpschaaf kevinpschaaf changed the title Add legacy-data-mixin as 1.x->2.x migration aide. Fixes #5262. [2.x] Add legacy-data-mixin as 1.x->2.x migration aide. Fixes #5262. Aug 21, 2018
@vdonich
Copy link

vdonich commented Oct 29, 2018

Looks like Computed bindings (https://www.polymer-project.org/1.0/docs/devguide/data-binding#annotated-computed) are not handled by this mixin.
Here's jsfiddle that demonstrates it: https://jsfiddle.net/gaj0umLs/13/

@kevinpschaaf
Copy link
Member Author

@vdonich Thanks for the report. I confirmed that TemplateInstances created from dom-if/dom-repeat don't get the LegacyDataMixin applied, hence the undefined check isn't running. Will look into a fix for that.

@kevinpschaaf
Copy link
Member Author

Addressed the dom-if/dom-repeat issue pointed out by @vdonich here: 55e9dfd

@kevinpschaaf kevinpschaaf merged commit 79de83c into 2.x Nov 2, 2018
@kevinpschaaf kevinpschaaf deleted the 5262-kschaaf-legacy-undefined branch November 2, 2018 06:20
@vdonich
Copy link

vdonich commented Nov 2, 2018

I've hit another snag when using wildcards.

Observer is being called despite undefined property value.
Change record is valid, but all value-related properties such as changeRecord.base and changeRecord.value are undefined:

{base: undefined, path: "alwaysUndefinedObject", value: undefined}

Here's the jsfiddle with example:
https://jsfiddle.net/gaj0umLs/26/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants