diff --git a/core/lib/buildListItems.js b/core/lib/buildListItems.js index a8aebbcda..af9ba783d 100644 --- a/core/lib/buildListItems.js +++ b/core/lib/buildListItems.js @@ -1,6 +1,30 @@ 'use strict'; -const _ = require('lodash'); +let _ = require('lodash'); //eslint-disable-line prefer-const + +const items = [ + 'zero', + 'one', + 'two', + 'three', + 'four', + 'five', + 'six', + 'seven', + 'eight', + 'nine', + 'ten', + 'eleven', + 'twelve', + 'thirteen', + 'fourteen', + 'fifteen', + 'sixteen', + 'seventeen', + 'eighteen', + 'nineteen', + 'twenty', +]; module.exports = function(container) { //combine all list items into one structure @@ -10,17 +34,19 @@ module.exports = function(container) { list.push(container.listitems[item]); } } - container.listItemArray = _.shuffle(list); + const listItemArray = _.shuffle(list); - for (let i = 1; i <= container.listItemArray.length; i++) { + for (let i = 1; i <= listItemArray.length; i++) { const tempItems = []; if (i === 1) { - tempItems.push(container.listItemArray[0]); - container.listitems['' + i] = tempItems; + tempItems.push(listItemArray[0]); + container.listitems['listItems-' + items[i]] = tempItems; + delete container.listitems[i]; } else { for (let c = 1; c <= i; c++) { - tempItems.push(container.listItemArray[c - 1]); - container.listitems['' + i] = tempItems; + tempItems.push(listItemArray[c - 1]); + container.listitems['listItems-' + items[i]] = tempItems; + delete container.listitems[i]; } } } diff --git a/core/lib/list_item_hunter.js b/core/lib/list_item_hunter.js index 49ac87f70..167c8e55c 100644 --- a/core/lib/list_item_hunter.js +++ b/core/lib/list_item_hunter.js @@ -1,244 +1,38 @@ 'use strict'; const list_item_hunter = function() { - const extend = require('util')._extend; - const _ = require('lodash'); - const smh = require('./style_modifier_hunter'); - const jsonCopy = require('./json_copy'); - const Pattern = require('./object_factory').Pattern; - const logger = require('./log'); - const parseLink = require('./parseLink'); - const getPartial = require('./get'); - const render = require('./render'); - - const style_modifier_hunter = new smh(); - const items = [ - 'zero', - 'one', - 'two', - 'three', - 'four', - 'five', - 'six', - 'seven', - 'eight', - 'nine', - 'ten', - 'eleven', - 'twelve', - 'thirteen', - 'fourteen', - 'fifteen', - 'sixteen', - 'seventeen', - 'eighteen', - 'nineteen', - 'twenty', - ]; - function processListItemPartials(pattern, patternlab) { + function processListItemPartials(pattern) { //find any listitem blocks const matches = pattern.findListItems(); if (matches !== null) { - return matches.reduce((previousMatchPromise, liMatch) => { + return matches.reduce((previousMatchPromise, liMatchStart) => { return previousMatchPromise.then(() => { logger.debug( - `found listItem of size ${liMatch} inside ${pattern.patternPartial}` + `found listItem of size ${liMatchStart} inside ${ + pattern.patternPartial + }` ); - //find the boundaries of the block - const loopNumberString = liMatch - .split('.')[1] - .split('}')[0] - .trim(); - const end = liMatch.replace('#', '/'); - const patternBlock = pattern.template - .substring( - pattern.template.indexOf(liMatch) + liMatch.length, - pattern.template.indexOf(end) - ) - .trim(); - - //build arrays that repeat the block, however large we need to - const repeatedBlockTemplate = []; - - //what we will eventually replace our template's listitems block with - let repeatedBlockHtml = ''; - - for (let i = 0; i < items.indexOf(loopNumberString); i++) { - logger.debug( - `list item(s) in pattern ${ - pattern.patternPartial - }, adding ${patternBlock} to repeatedBlockTemplate` - ); - repeatedBlockTemplate.push(patternBlock); - } - - //check for a local listitems.json file - let listData; - try { - listData = jsonCopy( - patternlab.listitems, - 'config.paths.source.data listitems' - ); - } catch (err) { - logger.warning( - `There was an error parsing JSON for ${pattern.relPath}` - ); - logger.warning(err); - } - - listData = _.merge(listData, pattern.listitems); - listData = parseLink( - patternlab, - listData, - 'listitems.json + any pattern listitems.json' + //we found a listitem match + //replace it's beginning listitems.number with -number + const newStart = liMatchStart.replace('.', '-'); + pattern.extendedTemplate = pattern.extendedTemplate.replace( + liMatchStart, + newStart ); - //iterate over each copied block, rendering its contents - const allBlocks = repeatedBlockTemplate.reduce( - (previousPromise, currentBlockTemplate, index) => { - let thisBlockTemplate = currentBlockTemplate; - - return previousPromise - .then(() => { - //combine listItem data with pattern data with global data - const itemData = - listData['' + items.indexOf(loopNumberString)]; //this is a property like "2" - let globalData; - let localData; - try { - globalData = jsonCopy( - patternlab.data, - 'config.paths.source.data global data' - ); - localData = jsonCopy( - pattern.jsonFileData, - `${pattern.patternPartial} data` - ); - } catch (err) { - logger.warning( - `There was an error parsing JSON for ${pattern.relPath}` - ); - logger.warning(err); - } - - let allData = _.merge(globalData, localData); - allData = _.merge( - allData, - itemData !== undefined ? itemData[index] : {} - ); //itemData could be undefined if the listblock contains no partial, just markup - allData.link = extend({}, patternlab.data.link); - - //check for partials within the repeated block - const foundPartials = Pattern.createEmpty({ - template: thisBlockTemplate, - }).findPartials(); - - let renderPromise = undefined; - - if (foundPartials && foundPartials.length > 0) { - for (let j = 0; j < foundPartials.length; j++) { - //get the partial - const partialName = foundPartials[j].match( - /([\w\-\.\/~]+)/g - )[0]; - const partialPattern = getPartial( - partialName, - patternlab - ); - - //create a copy of the partial so as to not pollute it after the get_pattern_by_key call. - let cleanPartialPattern; - try { - cleanPartialPattern = JSON.parse( - JSON.stringify(partialPattern) - ); - cleanPartialPattern = jsonCopy( - partialPattern, - `partial pattern ${partialName}` - ); - } catch (err) { - logger.warning( - `There was an error parsing JSON for ${ - pattern.relPath - }` - ); - logger.warning(err); - } - - //if we retrieved a pattern we should make sure that its extendedTemplate is reset. looks to fix #356 - cleanPartialPattern.extendedTemplate = - cleanPartialPattern.template; - - //if partial has style modifier data, replace the styleModifier value - if (foundPartials[j].indexOf(':') > -1) { - style_modifier_hunter.consume_style_modifier( - cleanPartialPattern, - foundPartials[j], - patternlab - ); - } - - //replace its reference within the block with the extended template - thisBlockTemplate = thisBlockTemplate.replace( - foundPartials[j], - cleanPartialPattern.extendedTemplate - ); - } - - //render with data - renderPromise = render( - Pattern.createEmpty({ template: thisBlockTemplate }), - allData, - patternlab.partials - ); - } else { - //just render with mergedData - renderPromise = render( - Pattern.createEmpty({ template: thisBlockTemplate }), - allData, - patternlab.partials - ); - } - - return renderPromise - .then(thisBlockHTML => { - //add the rendered HTML to our string - repeatedBlockHtml = repeatedBlockHtml + thisBlockHTML; - }) - .catch(reason => { - logger.error(reason); - }); - }) - .catch(reason => { - logger.error(reason); - }); - }, - Promise.resolve() + //replace it's ending listitems.number with -number + const liMatchEnd = liMatchStart.replace('#', '/'); + const newEnd = liMatchEnd.replace('.', '-'); + pattern.extendedTemplate = pattern.extendedTemplate.replace( + liMatchEnd, + newEnd ); - return allBlocks - .then(() => { - //replace the block with our generated HTML - const repeatingBlock = pattern.extendedTemplate.substring( - pattern.extendedTemplate.indexOf(liMatch), - pattern.extendedTemplate.indexOf(end) + end.length - ); - pattern.extendedTemplate = pattern.extendedTemplate.replace( - repeatingBlock, - repeatedBlockHtml - ); - - //update the extendedTemplate in the partials object in case this pattern is consumed later - patternlab.partials[pattern.patternPartial] = - pattern.extendedTemplate; - }) - .catch(reason => { - logger.error(reason); - }); + return Promise.resolve(); }); }, Promise.resolve()); } else { @@ -247,8 +41,8 @@ const list_item_hunter = function() { } return { - process_list_item_partials: function(pattern, patternlab) { - return processListItemPartials(pattern, patternlab); + process_list_item_partials: function(pattern) { + return processListItemPartials(pattern); }, }; }; diff --git a/core/lib/patternlab.js b/core/lib/patternlab.js index 39cf984ba..3f8662141 100644 --- a/core/lib/patternlab.js +++ b/core/lib/patternlab.js @@ -11,6 +11,7 @@ const packageInfo = require('../../package.json'); const buildListItems = require('./buildListItems'); const dataLoader = require('./data_loader')(); const logger = require('./log'); +const parseLink = require('./parseLink'); const processIterative = require('./processIterative'); const processRecursive = require('./processRecursive'); const jsonCopy = require('./json_copy'); @@ -363,13 +364,16 @@ module.exports = class PatternLab { //render the pattern, but first consolidate any data we may have let allData; - try { - allData = jsonCopy(this.data, 'config.paths.source.data global data'); - } catch (err) { - logger.info('There was an error parsing JSON for ' + pattern.relPath); - logger.info(err); - } - allData = _.merge(allData, pattern.jsonFileData); + + let allListItems = _.merge({}, this.listitems, pattern.listitems); + allListItems = parseLink( + this, + allListItems, + 'listitems.json + any pattern listitems.json' + ); + + allData = _.merge({}, this.data, pattern.jsonFileData); + allData = _.merge({}, allData, allListItems); allData.cacheBuster = this.cacheBuster; /////////////// diff --git a/test/buildListItems_tests.js b/test/buildListItems_tests.js new file mode 100644 index 000000000..97a78d256 --- /dev/null +++ b/test/buildListItems_tests.js @@ -0,0 +1,85 @@ +'use strict'; + +const tap = require('tap'); +const rewire = require('rewire'); + +const listItems = require('./files/_data/listItems.json'); +const buildlistItems = rewire('../core/lib/buildListItems'); + +const _Mock = { + shuffle: function(list) { + return list; + }, +}; + +//set our mocks in place of usual require() +buildlistItems.__set__({ + _: _Mock, +}); + +tap.test( + 'buildlistItems transforms container of listItems with one value', + test => { + // do this to avoid the shuffling for now + const container = Object.assign({}, { listitems: { '1': listItems['1'] } }); + buildlistItems(container); + test.same(container.listitems, { + 'listItems-one': [ + { + title: 'tA', + description: 'dA', + message: 'mA', + }, + ], + }); + test.end(); + } +); + +tap.test( + 'buildlistItems transforms container of listItems with three values', + test => { + // do this to avoid the shuffling for now + const container = { listitems: listItems }; + buildlistItems(container); + test.same(container.listitems, { + 'listItems-one': [ + { + title: 'tA', + description: 'dA', + message: 'mA', + }, + ], + 'listItems-two': [ + { + title: 'tA', + description: 'dA', + message: 'mA', + }, + { + title: 'tB', + description: 'dB', + message: 'mB', + }, + ], + 'listItems-three': [ + { + title: 'tA', + description: 'dA', + message: 'mA', + }, + { + title: 'tB', + description: 'dB', + message: 'mB', + }, + { + title: 'tC', + description: 'dC', + message: 'mC', + }, + ], + }); + test.end(); + } +); diff --git a/test/files/_data/listitems.json b/test/files/_data/listitems.json index 3187a7a8d..0cb4c328d 100644 --- a/test/files/_data/listitems.json +++ b/test/files/_data/listitems.json @@ -1,11 +1,17 @@ { "1": { - "title": "Nullizzle shizznit velizzle, hizzle, suscipit own yo', gravida vizzle, arcu." + "title": "tA", + "description": "dA", + "message": "mA" }, "2": { - "title": "Veggies sunt bona vobis, proinde vos postulo" + "title": "tB", + "description": "dB", + "message": "mB" }, "3": { - "title": "Bacon ipsum dolor sit amet turducken strip steak beef ribs shank" + "title": "tC", + "description": "dC", + "message": "mC" } } diff --git a/test/files/_patterns/00-test/553-repeatedListItems.mustache b/test/files/_patterns/00-test/553-repeatedListItems.mustache new file mode 100644 index 000000000..8362b6d31 --- /dev/null +++ b/test/files/_patterns/00-test/553-repeatedListItems.mustache @@ -0,0 +1,2 @@ +{{# listItems.three }}A{{/ listItems.three }} +{{# listItems.three }}B{{/ listItems.three }} diff --git a/test/files/_patterns/00-test/listWithListItems.listitems.json b/test/files/_patterns/00-test/listWithListItems.listitems.json new file mode 100644 index 000000000..01497948f --- /dev/null +++ b/test/files/_patterns/00-test/listWithListItems.listitems.json @@ -0,0 +1,11 @@ +{ + "1": { + "title": "tX" + }, + "2": { + "title": "tY" + }, + "3": { + "title": "tZ" + } +} diff --git a/test/files/_patterns/00-test/listWithListItems.mustache b/test/files/_patterns/00-test/listWithListItems.mustache new file mode 100644 index 000000000..34699f54e --- /dev/null +++ b/test/files/_patterns/00-test/listWithListItems.mustache @@ -0,0 +1,3 @@ +{{#listItems.three}} + {{> test-mirror }} +{{/listItems.three}} diff --git a/test/files/_patterns/00-test/listWithPartial.mustache b/test/files/_patterns/00-test/listWithPartial.mustache new file mode 100644 index 000000000..5a734227a --- /dev/null +++ b/test/files/_patterns/00-test/listWithPartial.mustache @@ -0,0 +1,3 @@ +{{#listItems.two}} +{{> test-comment }} +{{/listItems.two}} diff --git a/test/files/_patterns/00-test/mirror.mustache b/test/files/_patterns/00-test/mirror.mustache new file mode 100644 index 000000000..31ea13421 --- /dev/null +++ b/test/files/_patterns/00-test/mirror.mustache @@ -0,0 +1 @@ +{{title}}{{description}} diff --git a/test/index_tests.js b/test/index_tests.js index 4d95a58a6..12b2e5b03 100644 --- a/test/index_tests.js +++ b/test/index_tests.js @@ -127,6 +127,82 @@ tap.test('buildPatterns', function() { test.end(); }); + tap.test('uses global listItem property', test => { + var pattern = get('test-listWithPartial', patternlab); + console.log(pattern.patternPartialCode); + let assertionCount = 0; + ['dA', 'dB', 'dC'].forEach(d => { + if (pattern.patternPartialCode.indexOf(d) > -1) { + assertionCount++; + } + }); + test.ok(assertionCount === 2); + test.end(); + }); + + tap.test( + 'overwrites listItem property if that property is in local .listitem.json', + test => { + var pattern = get('test-listWithListItems', patternlab); + test.ok(pattern.patternPartialCode.indexOf('tX') > -1); + test.ok(pattern.patternPartialCode.indexOf('tY') > -1); + test.ok(pattern.patternPartialCode.indexOf('tZ') > -1); + + test.end(); + } + ); + + tap.test( + 'uses global listItem property after merging local .listitem.json', + test => { + var pattern = get('test-listWithListItems', patternlab); + test.ok(pattern.patternPartialCode.indexOf('dA') > -1); + test.ok(pattern.patternPartialCode.indexOf('dB') > -1); + test.ok(pattern.patternPartialCode.indexOf('dC') > -1); + test.end(); + } + ); + + tap.test( + 'correctly ignores bookended partials without a style modifier when the same partial has a style modifier between', + test => { + var pattern = get('test-bookend-listitem', patternlab); + test.equals( + util.sanitized(pattern.extendedTemplate), + util.sanitized(`