diff --git a/scripts/construct-leaflet-map.js b/scripts/construct-leaflet-map.js index e7fc82a..73d2f43 100644 --- a/scripts/construct-leaflet-map.js +++ b/scripts/construct-leaflet-map.js @@ -200,10 +200,29 @@ return output; }; + /** + * Trim whitespace + * @param {string} a + * @returns string + */ 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 +237,19 @@ 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 = liquid(att, function (match, obj) { + if (obj.link) { + return makeStringLink({ + href: obj.link, + textContent: obj.key, + }); + } + + return match; + }); + attControl.addAttribution(att); } } @@ -228,8 +260,6 @@ return div.innerText || str; }); - var templateRe = /\{ *(.*?) *\}/g; - /** * It interpolates variables in curly brackets (regex above) * @@ -243,8 +273,8 @@ return str; } - return str.replace(templateRe, 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; @@ -301,32 +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].split(': '); - var tagName = tag.shift(); - var tagValue = tag.join(': ') || 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/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/liquid.test.js b/tests/liquid.test.js new file mode 100644 index 0000000..aa93b03 --- /dev/null +++ b/tests/liquid.test.js @@ -0,0 +1,89 @@ +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('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 }'; + + plugin.liquid(str, observer); + + expect(observer).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + key: 'test', + default: 'yolo', + substr: '0,4', + lowercase: true, + }) + ); + }); + + 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', + }) + ); + }); +}); 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: {}[]; - }; - } -}