From ea307d0332274be55eec8199f0e593bf8d2d18b4 Mon Sep 17 00:00:00 2001 From: bozdoz Date: Mon, 19 Sep 2022 00:42:56 -0300 Subject: [PATCH 1/3] initial links in individual attributions --- scripts/construct-leaflet-map.js | 42 +++++++++++++++--- tests/addAttribution.test.js | 75 ++++++++++++++++++++++++++++++++ tests/types.d.ts | 41 ----------------- 3 files changed, 111 insertions(+), 47 deletions(-) create mode 100644 tests/addAttribution.test.js delete mode 100644 tests/types.d.ts diff --git a/scripts/construct-leaflet-map.js b/scripts/construct-leaflet-map.js index e7fc82a..d79b606 100644 --- a/scripts/construct-leaflet-map.js +++ b/scripts/construct-leaflet-map.js @@ -200,10 +200,26 @@ return output; }; + var liquidRe = /{ *(.*?) *}/g; + function trim(a) { return a.trim ? a.trim() : a.replace(/^\s+|\s+$/gm, ''); } + /** + * Gets string of anchor for Leaflet's consumption + * @param {Record<'href' | 'textContent', string>} atts + */ + function makeStringLink(atts) { + const a = document.createElement('a'); + a.href = atts.href; + a.textContent = atts.textContent; + a.target = '_blank'; + a.rel = 'noopener noreferrer'; + + return a.outerHTML; + } + function addAttributionToMap(attribution, map) { if (!attribution) { return; @@ -218,6 +234,21 @@ for (var i = 0, len = attributions.length; i < len; i++) { var att = trim(attributions[i]); + + // add liquid-style attribution: {WP | link: https://wordpress.com} + att = att.replace(liquidRe, function (match, key) { + var obj = liquid(key); + + if (obj.link) { + return makeStringLink({ + href: obj.link, + textContent: obj.key, + }); + } + + return match; + }); + attControl.addAttribution(att); } } @@ -228,8 +259,6 @@ return div.innerText || str; }); - var templateRe = /\{ *(.*?) *\}/g; - /** * It interpolates variables in curly brackets (regex above) * @@ -243,7 +272,7 @@ return str; } - return str.replace(templateRe, function (match, key) { + return str.replace(liquidRe, function (match, key) { var obj = liquid(key); var value = parseKey(data, obj.key); if (value == null) { @@ -314,9 +343,10 @@ var key = tags.shift(); for (var i = 0, len = tags.length; i < len; i++) { - var tag = tags[i].split(': '); - var tagName = tag.shift(); - var tagValue = tag.join(': ') || true; + var tag = tags[i]; + var delimiter = tag.indexOf(':'); + var tagName = trim(tag.slice(0, delimiter)); + var tagValue = trim(tag.slice(delimiter + 1)) || true; obj[tagName] = tagValue; } diff --git a/tests/addAttribution.test.js b/tests/addAttribution.test.js new file mode 100644 index 0000000..cedac32 --- /dev/null +++ b/tests/addAttribution.test.js @@ -0,0 +1,75 @@ +require('../scripts/construct-leaflet-map'); + +const plugin = window.WPLeafletMapPlugin; + +// mock leaflet functions (kind of long-winded) +const addAttribution = jest.fn(); +window.L = { + control: { + attribution: () => ({ + addTo: () => ({ + addAttribution, + }), + }), + }, + map: jest.fn(), +}; + +const originalAttribution = + 'Leaflet; © OpenStreetMap contributors'; + +describe('addAttribution', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('does not call addAttribution if not present in options', () => { + plugin.createMap({}); + + expect(L.map).toHaveBeenCalledWith(undefined, expect.any(Object)); + expect(addAttribution).not.toHaveBeenCalled(); + }); + + it('calls addAttribution with original (backwards-compatible) HTML string', () => { + plugin.createMap({ + attribution: originalAttribution, + }); + + const attributions = originalAttribution.split('; '); + + expect(addAttribution).toHaveBeenCalledWith(attributions[0]); + expect(addAttribution).toHaveBeenCalledWith(attributions[1]); + }); + + it('calls original attribution with markdown string', () => { + const attribution = + '{Leaflet | link: http://leafletjs.com}; © {OpenStreetMap | link: http://www.openstreetmap.org/copyright} contributors'; + + plugin.createMap({ + attribution, + }); + + const attributions = originalAttribution.split('; '); + + expect(addAttribution).toHaveBeenCalledWith( + 'Leaflet' + ); + expect(addAttribution).toHaveBeenCalledWith( + '© OpenStreetMap contributors' + ); + }); + + it('works without a space after the filter', () => { + const attribution = '{Leaflet | link:http://leafletjs.com}'; + + plugin.createMap({ + attribution, + }); + + const attributions = originalAttribution.split('; '); + + expect(addAttribution).toHaveBeenCalledWith( + 'Leaflet' + ); + }); +}); diff --git a/tests/types.d.ts b/tests/types.d.ts deleted file mode 100644 index e1cea4a..0000000 --- a/tests/types.d.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * VSCode sometimes recognizes this file 🤷‍♀️ - * maybe the tests should just be written in typescript - */ -interface Options { - fitBounds: boolean; - attribution: string; -} - -type LeafletMap = {}; -type LeafletFeatureGroup = {}; - -// helps with some intellisense -export declare global { - interface Window { - WPLeafletMapPlugin: { - push(cb: () => void): void; - unshift(cb: () => void): void; - init(): void; - createMap(options: Options): LeafletMap; - createImageMap(options: Options): LeafletMap; - getCurrentMap(): LeafletMap; - getCurrentGroup(): LeafletFeatureGroup; - getGroup(map: LeafletMap): LeafletFeatureGroup; - newMarkerGroup(map: LeafletMap): LeafletFeatureGroup; - propsToTable(props: {}): string; - template(str: string, data: {}): string; - waitForSVG(cb: () => void): void; - waitForAjax(cb: () => void): void; - createScale(options: {}): void; - maps: LeafletMap[]; - images: LeafletMap[]; - markergroups: Record; - markers: {}[]; - lines: {}[]; - polygons: {}[]; - circles: {}[]; - geojson: {}[]; - }; - } -} From 5b06240213a98261dec2d28818fcec40280c8683 Mon Sep 17 00:00:00 2001 From: bozdoz Date: Mon, 19 Sep 2022 15:25:03 -0300 Subject: [PATCH 2/3] more testing for liquid tags --- scripts/construct-leaflet-map.js | 60 +++++++++++++++++------------- tests/liquid.test.js | 63 ++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 25 deletions(-) create mode 100644 tests/liquid.test.js diff --git a/scripts/construct-leaflet-map.js b/scripts/construct-leaflet-map.js index d79b606..73d2f43 100644 --- a/scripts/construct-leaflet-map.js +++ b/scripts/construct-leaflet-map.js @@ -200,8 +200,11 @@ return output; }; - var liquidRe = /{ *(.*?) *}/g; - + /** + * Trim whitespace + * @param {string} a + * @returns string + */ function trim(a) { return a.trim ? a.trim() : a.replace(/^\s+|\s+$/gm, ''); } @@ -236,9 +239,7 @@ var att = trim(attributions[i]); // add liquid-style attribution: {WP | link: https://wordpress.com} - att = att.replace(liquidRe, function (match, key) { - var obj = liquid(key); - + att = liquid(att, function (match, obj) { if (obj.link) { return makeStringLink({ href: obj.link, @@ -272,8 +273,8 @@ return str; } - return str.replace(liquidRe, function (match, key) { - var obj = liquid(key); + // @since 2.21.0 allow for a `default` filter for missing properties + return liquid(str, function (match, obj) { var value = parseKey(data, obj.key); if (value == null) { return obj.default || match; @@ -330,33 +331,42 @@ return value; } + var liquidRe = /{+ *(.*?) *}+/g; + /** * parses liquid tags from a string * * @param {string} str */ - function liquid(str) { - var tags = str.split(' | '); - var obj = {}; - - // removes initial variable from array - var key = tags.shift(); - - for (var i = 0, len = tags.length; i < len; i++) { - var tag = tags[i]; - var delimiter = tag.indexOf(':'); - var tagName = trim(tag.slice(0, delimiter)); - var tagValue = trim(tag.slice(delimiter + 1)) || true; - - obj[tagName] = tagValue; - } + function liquid(str, callback) { + return str.replace(liquidRe, function (match, group) { + /** @type string[] */ + var tags = group.split(' | '); + var obj = {}; + + // removes initial variable from array + var filter = tags.shift(); + + for (var i = 0, len = tags.length; i < len; i++) { + var tag = tags[i]; + var delimiter = tag.indexOf(':'); + var tagName = trim(delimiter === -1 ? tag : tag.slice(0, delimiter)); + var tagValue = + delimiter === -1 ? true : trim(tag.slice(delimiter + 1)); + + obj[tagName] = tagValue; + } - // always preserve the original string - obj.key = key; + // always preserve the original string + obj.key = filter; - return obj; + return callback(match, obj); + }); } + // export for tests + this.liquid = liquid; + function waitFor(prop, cb) { if (typeof L !== 'undefined' && typeof L[prop] !== 'undefined') { cb(); diff --git a/tests/liquid.test.js b/tests/liquid.test.js new file mode 100644 index 0000000..06a1fe7 --- /dev/null +++ b/tests/liquid.test.js @@ -0,0 +1,63 @@ +require('../scripts/construct-leaflet-map'); + +const plugin = window.WPLeafletMapPlugin; + +describe('liquid', () => { + const observer = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('parses a boolean filter', () => { + const str = '{ test | isBoolean }'; + + plugin.liquid(str, observer); + + expect(observer).toHaveBeenCalledWith( + str, + expect.objectContaining({ + key: 'test', + isBoolean: true, + }) + ); + }); + + it('also parses when liquid tag is doubled', () => { + const str = '{{ test }}'; + + plugin.liquid(str, observer); + + expect(observer).toHaveBeenCalledWith( + str, + expect.objectContaining({ + key: 'test', + }) + ); + }); + + it('does not parse when tag is malformed', () => { + const str = '{ test '; + + const output = plugin.liquid(str, observer); + + expect(observer).not.toHaveBeenCalled(); + expect(output).toEqual(str); + }); + + it('accepts multiple filters', () => { + const str = '{ test | default: yolo | substr: 0,4 | lowercase }'; + + plugin.liquid(str, observer); + + expect(observer).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + key: 'test', + default: 'yolo', + substr: '0,4', + lowercase: true, + }) + ); + }); +}); From 95bd64a837d5f226d019180194edfcdc9a1f8d27 Mon Sep 17 00:00:00 2001 From: bozdoz Date: Mon, 19 Sep 2022 20:48:34 -0300 Subject: [PATCH 3/3] test for key as a filter --- tests/liquid.test.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/liquid.test.js b/tests/liquid.test.js index 06a1fe7..aa93b03 100644 --- a/tests/liquid.test.js +++ b/tests/liquid.test.js @@ -45,6 +45,19 @@ describe('liquid', () => { expect(output).toEqual(str); }); + it('does not parse when bar is missing whitespace', () => { + const str = '{ test|isBoolean }'; + + const output = plugin.liquid(str, observer); + + expect(observer).toHaveBeenCalledWith( + expect.any(String), + expect.not.objectContaining({ + isBoolean: true, + }) + ); + }); + it('accepts multiple filters', () => { const str = '{ test | default: yolo | substr: 0,4 | lowercase }'; @@ -60,4 +73,17 @@ describe('liquid', () => { }) ); }); + + it('does not have key as a filter', () => { + const str = '{ key | key: not key }'; + + plugin.liquid(str, observer); + + expect(observer).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + key: 'key', + }) + ); + }); });