From a5b93ebb257b2d2bb5ce16d39dab6d493ccd6feb Mon Sep 17 00:00:00 2001 From: Michael Rawlings Date: Mon, 25 Jun 2018 21:35:39 -0700 Subject: [PATCH] fix for loop autokeys (#1067) --- .gitignore | 1 + package.json | 2 +- .../TransformHelper/assignComponentId.js | 77 +++++++++++++++++++ .../fixtures-html/at-tags-dynamic/expected.js | 4 + .../compiler/fixtures-html/simple/expected.js | 4 + .../compiler/fixtures-vdom/simple/expected.js | 8 +- .../index.marko | 21 +++++ .../test.js | 62 +++++++++++++++ .../fixtures-html/auto-key-els/expected.js | 10 ++- .../fixtures-html/macro-widget/expected.js | 4 + .../preserve-no-update-for-loop/tests.js | 5 +- 11 files changed, 189 insertions(+), 9 deletions(-) create mode 100644 test/components-browser/fixtures/widget-preserve-all-state-when-rendered-in-loop/index.marko create mode 100644 test/components-browser/fixtures/widget-preserve-all-state-when-rendered-in-loop/test.js diff --git a/.gitignore b/.gitignore index af85823eb..b74bd67c0 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ coverage /dist/ /test-dist/ /test-generated/ +.vs/ \ No newline at end of file diff --git a/package.json b/package.json index bf11a52d1..f58c21a4f 100644 --- a/package.json +++ b/package.json @@ -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 .", diff --git a/src/components/taglib/TransformHelper/assignComponentId.js b/src/components/taglib/TransformHelper/assignComponentId.js index 7a61de874..68885a39f 100644 --- a/src/components/taglib/TransformHelper/assignComponentId.js +++ b/src/components/taglib/TransformHelper/assignComponentId.js @@ -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; + } +}; diff --git a/test/compiler/fixtures-html/at-tags-dynamic/expected.js b/test/compiler/fixtures-html/at-tags-dynamic/expected.js index 47ef654da..c23d67c03 100644 --- a/test/compiler/fixtures-html/at-tags-dynamic/expected.js +++ b/test/compiler/fixtures-html/at-tags-dynamic/expected.js @@ -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!"); diff --git a/test/compiler/fixtures-html/simple/expected.js b/test/compiler/fixtures-html/simple/expected.js index 750b201a3..3c28356cf 100644 --- a/test/compiler/fixtures-html/simple/expected.js +++ b/test/compiler/fixtures-html/simple/expected.js @@ -33,7 +33,11 @@ function render(input, out, __component, component, state) { if (input.colors.length) { out.w("