fix for loop autokeys (#1067)

This commit is contained in:
Michael Rawlings 2018-06-25 21:35:39 -07:00 committed by GitHub
parent a78c54d25e
commit a5b93ebb25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 189 additions and 9 deletions

1
.gitignore vendored
View File

@ -19,3 +19,4 @@ coverage
/dist/
/test-dist/
/test-generated/
.vs/

View File

@ -10,7 +10,7 @@
"precommit": "lint-staged",
"test": "npm run lint -s && npm run mocha -s",
"test-ci": "npm run check-format && npm run test-generate-coverage",
"mocha": "mocha --timeout 10000 --max-old-space-size=768 ./test/*/*.test.js || exit 1",
"mocha": "mocha --timeout 10000 --max-old-space-size=768 ./test/*/*.test.js",
"test-coverage": "npm run test-generate-coverage && nyc report --reporter=html && opn ./coverage/index.html",
"test-generate-coverage": "nyc -asc npm test",
"lint": "eslint .",

View File

@ -106,6 +106,7 @@ module.exports = function assignComponentId(isRepeated) {
}
} else {
// Case 3 - We need to add a unique auto key
let parentForKey = getParentForKeyVar(el, this);
let uniqueKey = this.nextUniqueId();
nestedIdExpression = isRepeated
@ -114,6 +115,19 @@ module.exports = function assignComponentId(isRepeated) {
idExpression = builder.literal(uniqueKey.toString());
if (parentForKey) {
idExpression = builder.binaryExpression(
idExpression,
"+",
parentForKey
);
nestedIdExpression = builder.binaryExpression(
nestedIdExpression,
"+",
parentForKey
);
}
if (isCustomTag) {
this.getComponentArgs().setKey(nestedIdExpression);
} else {
@ -168,3 +182,66 @@ module.exports = function assignComponentId(isRepeated) {
return this.componentIdInfo;
};
const getParentForKeyVar = (el, transformHelper) => {
const context = transformHelper.context;
const builder = context.builder;
const parentFor = getParentFor(el);
if (!parentFor) return null;
if (parentFor.keyVar) return parentFor.keyVar;
const keyExpression =
getChildKey(parentFor) || createIndexKey(parentFor, transformHelper);
const bracketedKeyExpression = builder.binaryExpression(
builder.literal("["),
"+",
builder.binaryExpression(keyExpression, "+", builder.literal("]"))
);
const varName = "keyscope__" + transformHelper.nextUniqueId();
const varDeclaration = builder.var(varName, bracketedKeyExpression);
parentFor.prependChild(varDeclaration);
return (parentFor.keyVar = builder.identifier(varName));
};
const createIndexKey = (forNode, transformHelper) => {
const context = transformHelper.context;
const builder = context.builder;
const varName = "for__" + transformHelper.nextUniqueId();
const intialize = builder.parseStatement(`var ${varName} = 0;`);
const parentForKey = getParentForKeyVar(forNode, transformHelper);
forNode.insertSiblingBefore(intialize);
let keyExpression = builder.parseExpression(`${varName}++`);
if (parentForKey) {
keyExpression = builder.binaryExpression(
keyExpression,
"+",
parentForKey
);
}
return keyExpression;
};
const getParentFor = el => {
let current = el;
while ((current = current.parentNode)) {
if (current.tagName === "for") return current;
}
};
const getChildKey = el => {
let current = el.firstChild;
while (current) {
if (current.key) return current.key;
if (current.hasAttribute && current.hasAttribute("key")) {
return current.getAttributeValue("key");
}
current = current.nextSibling;
}
};

View File

@ -21,7 +21,11 @@ function render(input, out, __component, component, state) {
hello_tag(marko_mergeNestedTagsHelper({
renderBody: function renderBody(out, hello0) {
var for__1 = 0;
marko_forEach(input.colors, function(color) {
var keyscope__2 = "[" + ((for__1++) + "]");
hello_foo_nested_tag({
renderBody: function renderBody(out) {
out.w("Foo!");

View File

@ -33,7 +33,11 @@ function render(input, out, __component, component, state) {
if (input.colors.length) {
out.w("<ul>");
var for__4 = 0;
marko_forEach(input.colors, function(color) {
var keyscope__5 = "[" + ((for__4++) + "]");
out.w("<li>" +
marko_escapeXml(color) +
"</li>");

View File

@ -17,7 +17,7 @@ var marko_template = module.exports = require("marko/src/vdom").t(),
i: marko_const_nextId()
})
.t("No colors!"),
marko_node1 = marko_createElement("DIV", null, "5", null, 1, 0, {
marko_node1 = marko_createElement("DIV", null, "7", null, 1, 0, {
i: marko_const_nextId()
})
.t("No colors!");
@ -47,8 +47,12 @@ function render(input, out, __component, component, state) {
if (input.colors.length) {
out.be("UL", null, "3", component);
var for__4 = 0;
marko_forEach(input.colors, function(color) {
out.e("LI", null, "4", component, 1)
var keyscope__5 = "[" + ((for__4++) + "]");
out.e("LI", null, "6" + keyscope__5, component, 1)
.t(color);
});

View File

@ -0,0 +1,21 @@
class {
onCreate() {
this.state = { items: [] };
}
addItem(id) {
this.state.items = this.state.items.concat(id);
}
removeItem(id) {
this.state.items = this.state.items.filter(item => item !== id);
}
}
<div>
<for(item in state.items)>
<div key=item>
<span/>
</div>
</for>
</div>

View File

@ -0,0 +1,62 @@
var expect = require("chai").expect;
module.exports = function(helpers) {
var component = helpers.mount(require.resolve("./index"), {});
var rootEl = component.getEl();
var itemIds = ["child-a", "child-b", "child-c"];
itemIds.forEach(id => component.addItem(id));
component.update();
var curLookup = getKeyLookup();
component.removeItem(itemIds[1]);
component.update();
ensurePreservedKeys(curLookup, (curLookup = getKeyLookup()));
component.removeItem(itemIds[0]);
component.update();
ensurePreservedKeys(curLookup, (curLookup = getKeyLookup()));
function getKeyLookup(els) {
els = els || rootEl.children;
var lookup = {};
for (var i = 0; i < els.length; i++) {
var el = els[i];
var component = helpers.getComponentForEl(el);
var key = el.___markoVElement.___key;
lookup[key] = {
el: el,
component: component,
children: getKeyLookup(el.children)
};
}
return lookup;
}
function ensurePreservedKeys(lookupA, lookupB, path) {
path = path || "";
for (var key in lookupB) {
if (lookupA[key]) {
var fullPath = path + " " + key;
expect(
lookupA[key].el === lookupB[key].el,
'unpreserved element "' + fullPath + '"'
).to.equal(true);
expect(
lookupA[key].component === lookupB[key].component,
'unpreserved component "' + fullPath + '"'
).to.equal(true);
ensurePreservedKeys(
lookupA[key].children,
lookupB[key].children,
fullPath
);
}
}
}
};

View File

@ -20,7 +20,11 @@ function render(input, out, __component, component, state) {
out.w("<div><span>A</span><ul>");
var for__2 = 0;
marko_forEach(colors, function(color) {
var keyscope__3 = "[" + ((for__2++) + "]");
out.w("<li>" +
marko_escapeXml(color) +
"</li>");
@ -28,13 +32,13 @@ function render(input, out, __component, component, state) {
out.w("</ul>");
var __key3 = __component.___nextKey("preservedP");
var __key5 = __component.___nextKey("preservedP");
out.w("<p>");
_preserve_tag({
bodyOnly: true,
key: __key3,
key: __key5,
renderBody: function renderBody(out) {
out.w(marko_escapeXml(Date.now()));
}
@ -44,7 +48,7 @@ function render(input, out, __component, component, state) {
include_tag({
_target: foo_template
}, out, __component, "5");
}, out, __component, "7");
}
marko_template._ = marko_renderer(render, {

View File

@ -28,11 +28,15 @@ function render(input, out, __component, component, state) {
out.w("<div>");
var for__2 = 0;
marko_forEach([
"red",
"green",
"blue"
], function(color) {
var keyscope__3 = "[" + ((for__2++) + "]");
macro_renderButton(color, out);
});

View File

@ -2,7 +2,7 @@ var path = require("path");
var expect = require("chai").expect;
describe(path.basename(__dirname), function() {
it.fails("should update correctly", function() {
it("should update correctly", function() {
var component = window.component;
var $el = component.getEl("root");
@ -17,6 +17,5 @@ describe(path.basename(__dirname), function() {
expect($el.innerHTML).to.eql(
'<li id="a"><span>a</span></li><li id="b"><span>b</span></li>'
);
}).details =
"Issue #952";
});
});